diff --git a/src/Orchard.Tests/Orchard.Framework.Tests.csproj b/src/Orchard.Tests/Orchard.Framework.Tests.csproj index 46a7febf0..b88a0e392 100644 --- a/src/Orchard.Tests/Orchard.Framework.Tests.csproj +++ b/src/Orchard.Tests/Orchard.Framework.Tests.csproj @@ -278,6 +278,7 @@ + diff --git a/src/Orchard.Tests/Stubs/StubWorkContextAccessor.cs b/src/Orchard.Tests/Stubs/StubWorkContextAccessor.cs index 807ffc968..89af557e5 100644 --- a/src/Orchard.Tests/Stubs/StubWorkContextAccessor.cs +++ b/src/Orchard.Tests/Stubs/StubWorkContextAccessor.cs @@ -77,6 +77,8 @@ namespace Orchard.Tests.Stubs { } public string BaseUrl { get; set;} + + public string SiteTimeZone { get; set; } } public class StubUser : IUser { diff --git a/src/Orchard.Tests/Time/TimeZoneSelectorTests.cs b/src/Orchard.Tests/Time/TimeZoneSelectorTests.cs new file mode 100644 index 000000000..b0cee916b --- /dev/null +++ b/src/Orchard.Tests/Time/TimeZoneSelectorTests.cs @@ -0,0 +1,85 @@ +using System; +using System.Web; +using Autofac; +using NUnit.Framework; +using Orchard.Tests.Stubs; +using Orchard.Time; + +namespace Orchard.Tests.Time { + [TestFixture] + public class TimeZoneProviderTests { + private IContainer _container; + private IWorkContextStateProvider _workContextStateProvider; + private TestTimeZoneSelector _timeZoneSelector; + + [SetUp] + public void Init() { + var builder = new ContainerBuilder(); + builder.RegisterInstance(_timeZoneSelector = new TestTimeZoneSelector()).As(); + builder.RegisterType().As(); + builder.RegisterType().As(); + builder.RegisterType().As(); + builder.RegisterType().As(); + + _container = builder.Build(); + _workContextStateProvider = _container.Resolve(); + } + + [Test] + public void ShouldProvideCurrentTimeZoneOnly() { + _timeZoneSelector.TimeZone = null; + var timeZone = _workContextStateProvider.Get("Foo"); + + Assert.That(timeZone, Is.Null); + } + + [Test] + public void DefaultTimeZoneIsLocal() { + _timeZoneSelector.Priority = -200; + var timeZone = _workContextStateProvider.Get("CurrentTimeZone"); + + Assert.That(timeZone(new StubWorkContext()), Is.EqualTo(TimeZoneInfo.Local)); + } + + [Test] + public void TimeZoneProviderReturnsTimeZoneFromSelector() { + _timeZoneSelector.Priority = 999; + _timeZoneSelector.TimeZone = TimeZoneInfo.Utc; + var timeZone = _workContextStateProvider.Get("CurrentTimeZone"); + + Assert.That(timeZone(new StubWorkContext()), Is.EqualTo(TimeZoneInfo.Utc)); + } + + } + + public class TestTimeZoneSelector : ITimeZoneSelector { + public TimeZoneInfo TimeZone { get; set; } + public int Priority { get; set; } + + public TimeZoneSelectorResult GetTimeZone(HttpContextBase context) { + return new TimeZoneSelectorResult { + Priority = Priority, + TimeZone= TimeZone + }; + } + } + + public class StubWorkContext : WorkContext { + public override T Resolve() { + throw new NotImplementedException(); + } + + public override bool TryResolve(out T service) { + throw new NotImplementedException(); + } + + public override T GetState(string name) { + return default(T); + } + + public override void SetState(string name, T value) { + + } + } +} + diff --git a/src/Orchard.Web/Core/Common/Shapes.cs b/src/Orchard.Web/Core/Common/Shapes.cs index 075ea07eb..3c0287efe 100644 --- a/src/Orchard.Web/Core/Common/Shapes.cs +++ b/src/Orchard.Web/Core/Common/Shapes.cs @@ -23,12 +23,12 @@ namespace Orchard.Core.Common { } [Shape] - public IHtmlString PublishedState(HtmlHelper html, DateTime createdDateTimeUtc, DateTime? publisheddateTimeUtc) { + public IHtmlString PublishedState(dynamic Display, DateTime createdDateTimeUtc, DateTime? publisheddateTimeUtc) { if (!publisheddateTimeUtc.HasValue) { return T("Draft"); } - return html.DateTime(createdDateTimeUtc); + return Display.DateTime(DateTimeUtc: createdDateTimeUtc); } [Shape] diff --git a/src/Orchard.Web/Core/Settings/Drivers/SiteSettingsPartDriver.cs b/src/Orchard.Web/Core/Settings/Drivers/SiteSettingsPartDriver.cs index b750f9bf5..81cd50525 100644 --- a/src/Orchard.Web/Core/Settings/Drivers/SiteSettingsPartDriver.cs +++ b/src/Orchard.Web/Core/Settings/Drivers/SiteSettingsPartDriver.cs @@ -1,4 +1,5 @@ -using System.Net; +using System.Linq; +using System.Net; using JetBrains.Annotations; using Orchard.ContentManagement; using Orchard.ContentManagement.Drivers; @@ -44,7 +45,8 @@ namespace Orchard.Core.Settings.Drivers { var model = new SiteSettingsPartViewModel { Site = site, - SiteCultures = _cultureManager.ListCultures() + SiteCultures = _cultureManager.ListCultures(), + TimeZones = TimeZoneInfo.GetSystemTimeZones() }; return ContentShape("Parts_Settings_SiteSettingsPart", @@ -55,7 +57,8 @@ namespace Orchard.Core.Settings.Drivers { var site = _siteService.GetSiteSettings().As(); var model = new SiteSettingsPartViewModel { Site = site, - SiteCultures = _cultureManager.ListCultures() + SiteCultures = _cultureManager.ListCultures(), + TimeZones = TimeZoneInfo.GetSystemTimeZones() }; var previousBaseUrl = model.Site.BaseUrl; diff --git a/src/Orchard.Web/Core/Settings/Migrations.cs b/src/Orchard.Web/Core/Settings/Migrations.cs index bc0ca6290..56b20fb39 100644 --- a/src/Orchard.Web/Core/Settings/Migrations.cs +++ b/src/Orchard.Web/Core/Settings/Migrations.cs @@ -91,9 +91,16 @@ namespace Orchard.Core.Settings { .Column("SiteCulture") .Column("ResourceDebugMode", c => c.WithDefault("FromAppSetting")) .Column("PageSize") + .Column("SiteTimeZone") ); - return 1; + SchemaBuilder.CreateTable("SiteSettings2PartRecord", + table => table + .ContentPartRecord() + .Column("BaseUrl", c => c.Unlimited()) + ); + + return 3; } public int UpdateFrom1() { @@ -105,5 +112,14 @@ namespace Orchard.Core.Settings { return 2; } + + public int UpdateFrom2() { + SchemaBuilder.AlterTable("SiteSettingsPartRecord", + table => table + .AddColumn("SiteTimeZone") + ); + + return 3; + } } } \ No newline at end of file diff --git a/src/Orchard.Web/Core/Settings/Models/SiteSettingsPart.cs b/src/Orchard.Web/Core/Settings/Models/SiteSettingsPart.cs index bf83d1f55..071f81bce 100644 --- a/src/Orchard.Web/Core/Settings/Models/SiteSettingsPart.cs +++ b/src/Orchard.Web/Core/Settings/Models/SiteSettingsPart.cs @@ -1,4 +1,5 @@ -using System.ComponentModel.DataAnnotations; +using System; +using System.ComponentModel.DataAnnotations; using Orchard.ContentManagement; using Orchard.Data.Conventions; using Orchard.Settings; @@ -45,6 +46,11 @@ namespace Orchard.Core.Settings.Models { set { Record.PageSize = value; } } + public string SiteTimeZone { + get { return Record.SiteTimeZone; } + set { Record.SiteTimeZone = value; } + } + [StringLengthMax] public string BaseUrl { get { diff --git a/src/Orchard.Web/Core/Settings/Models/SiteSettingsPartRecord.cs b/src/Orchard.Web/Core/Settings/Models/SiteSettingsPartRecord.cs index eb87877f5..6d85341be 100644 --- a/src/Orchard.Web/Core/Settings/Models/SiteSettingsPartRecord.cs +++ b/src/Orchard.Web/Core/Settings/Models/SiteSettingsPartRecord.cs @@ -1,4 +1,5 @@ -using Orchard.ContentManagement.Records; +using System; +using Orchard.ContentManagement.Records; using Orchard.Settings; namespace Orchard.Core.Settings.Models { @@ -24,5 +25,7 @@ namespace Orchard.Core.Settings.Models { public virtual ResourceDebugMode ResourceDebugMode { get; set; } public virtual int PageSize { get; set; } + + public virtual string SiteTimeZone { get; set; } } } \ No newline at end of file diff --git a/src/Orchard.Web/Core/Settings/Services/SiteService.cs b/src/Orchard.Web/Core/Settings/Services/SiteService.cs index 5fc76e488..603994413 100644 --- a/src/Orchard.Web/Core/Settings/Services/SiteService.cs +++ b/src/Orchard.Web/Core/Settings/Services/SiteService.cs @@ -36,6 +36,7 @@ namespace Orchard.Core.Settings.Services { item.Record.SiteSalt = Guid.NewGuid().ToString("N"); item.Record.SiteName = "My Orchard Project Application"; item.Record.PageTitleSeparator = " - "; + item.Record.SiteTimeZone = TimeZoneInfo.Local.Id; }).ContentItem; } diff --git a/src/Orchard.Web/Core/Settings/ViewModels/SiteSettingsPartViewModel.cs b/src/Orchard.Web/Core/Settings/ViewModels/SiteSettingsPartViewModel.cs index e140e8841..011e0f1ca 100644 --- a/src/Orchard.Web/Core/Settings/ViewModels/SiteSettingsPartViewModel.cs +++ b/src/Orchard.Web/Core/Settings/ViewModels/SiteSettingsPartViewModel.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Web.Mvc; using Orchard.Core.Settings.Models; using Orchard.Settings; @@ -7,6 +8,7 @@ namespace Orchard.Core.Settings.ViewModels { public class SiteSettingsPartViewModel { public SiteSettingsPart Site { get; set; } public IEnumerable SiteCultures { get; set; } + public IEnumerable TimeZones { get; set; } [HiddenInput(DisplayValue = false)] public int Id { @@ -47,5 +49,10 @@ namespace Orchard.Core.Settings.ViewModels { get { return Site.BaseUrl; } set { Site.BaseUrl = value; } } + + public string TimeZone { + get { return Site.SiteTimeZone; } + set { Site.SiteTimeZone = value; } + } } } diff --git a/src/Orchard.Web/Core/Settings/Views/EditorTemplates/Parts.Settings.SiteSettingsPart.cshtml b/src/Orchard.Web/Core/Settings/Views/EditorTemplates/Parts.Settings.SiteSettingsPart.cshtml index be3daaadb..afc4690b1 100644 --- a/src/Orchard.Web/Core/Settings/Views/EditorTemplates/Parts.Settings.SiteSettingsPart.cshtml +++ b/src/Orchard.Web/Core/Settings/Views/EditorTemplates/Parts.Settings.SiteSettingsPart.cshtml @@ -26,6 +26,12 @@ @Html.ValidationMessage("SiteCulture", "*")

@Html.ActionLink(T("Add or remove supported cultures for the site.").ToString(), "Culture")

+
+ + @Html.DropDownList("TimeZone", new[] { new SelectListItem { Text = T("Local to server").Text, Value = "" } }.Union(new SelectList(Model.TimeZones, "Id", "", Model.TimeZone))) + @Html.ValidationMessage("TimeZone", "*") + @T("Determines the default time zone which will should be used to display date and times.") +
@Html.EditorFor(x => x.PageTitleSeparator) diff --git a/src/Orchard.Web/Core/Shapes/DateTimeShapes.cs b/src/Orchard.Web/Core/Shapes/DateTimeShapes.cs index 7270d01fa..ab9c39fa6 100644 --- a/src/Orchard.Web/Core/Shapes/DateTimeShapes.cs +++ b/src/Orchard.Web/Core/Shapes/DateTimeShapes.cs @@ -7,22 +7,27 @@ using Orchard.Mvc.Html; using Orchard.Services; namespace Orchard.Core.Shapes { - public class DateTimeShapes : ISingletonDependency { + public class DateTimeShapes : IDependency { private readonly IClock _clock; + private readonly IWorkContextAccessor _workContextAccessor; - public DateTimeShapes(IClock clock) { + public DateTimeShapes( + IClock clock, + IWorkContextAccessor workContextAccessor + ) { _clock = clock; + _workContextAccessor = workContextAccessor; T = NullLocalizer.Instance; } public Localizer T { get; set; } [Shape] - public IHtmlString DateTimeRelative(HtmlHelper Html, DateTime dateTimeUtc) { + public IHtmlString DateTimeRelative(dynamic Display, DateTime dateTimeUtc) { var time = _clock.UtcNow - dateTimeUtc; if (time.TotalDays > 7) - return Html.DateTime(dateTimeUtc.ToLocalTime(), T("'on' MMM d yyyy 'at' h:mm tt")); + return Display.DateTime(DateTimeUtc: dateTimeUtc, CustomFormat: T("'on' MMM d yyyy 'at' h:mm tt")); if (time.TotalHours > 24) return T.Plural("1 day ago", "{0} days ago", time.Days); if (time.TotalMinutes > 60) @@ -34,5 +39,30 @@ namespace Orchard.Core.Shapes { return T("a moment ago"); } + + [Shape] + public IHtmlString DateTime(DateTime DateTimeUtc, LocalizedString CustomFormat) { + //using a LocalizedString forces the caller to use a localizable format + + if (CustomFormat == null || String.IsNullOrWhiteSpace(CustomFormat.Text)) { + return DateTime(DateTimeUtc, T("MMM d yyyy h:mm tt")); + } + + return new MvcHtmlString(ConvertToDisplayTime(DateTimeUtc).ToString(CustomFormat.Text)); + } + + /// + /// Converts a Coordinated Universal Time (UTC) to the time in the current time zone. + /// + /// The Coordinated Universal Time (UTC). + /// The date and time in the selected time zone. Its System.DateTime.Kind property is System.DateTimeKind.Utc if the current zone is System.TimeZoneInfo.Utc; otherwise, its System.DateTime.Kind property is System.DateTimeKind.Unspecified. + private DateTime ConvertToDisplayTime(DateTime dateTimeUtc) { + + // get the time zone for the current request + var timeZone = _workContextAccessor.GetContext().CurrentTimeZone; + + return TimeZoneInfo.ConvertTimeFromUtc(dateTimeUtc, timeZone); + } + } } diff --git a/src/Orchard.Web/Modules/Orchard.ArchiveLater/Views/Parts/ArchiveLater.Metadata.SummaryAdmin.cshtml b/src/Orchard.Web/Modules/Orchard.ArchiveLater/Views/Parts/ArchiveLater.Metadata.SummaryAdmin.cshtml index 35d3625f1..a9b35ac90 100644 --- a/src/Orchard.Web/Modules/Orchard.ArchiveLater/Views/Parts/ArchiveLater.Metadata.SummaryAdmin.cshtml +++ b/src/Orchard.Web/Modules/Orchard.ArchiveLater/Views/Parts/ArchiveLater.Metadata.SummaryAdmin.cshtml @@ -4,7 +4,7 @@
  • @T(@T("Unpublish on") - @Html.DateTime((DateTime)Model.ScheduledArchiveUtc.ToLocalTime(), T("M/d/yyyy h:mm tt")) + @Display.DateTime(DateTimeUtc: (DateTime)Model.ScheduledArchiveUtc.ToLocalTime(), CustomFormat: T("M/d/yyyy h:mm tt"))  | 
diff --git a/src/Orchard.Web/Modules/Orchard.Comments/Views/Admin/Details.cshtml b/src/Orchard.Web/Modules/Orchard.Comments/Views/Admin/Details.cshtml index 990ad322f..b63376f7f 100644 --- a/src/Orchard.Web/Modules/Orchard.Comments/Views/Admin/Details.cshtml +++ b/src/Orchard.Web/Modules/Orchard.Comments/Views/Admin/Details.cshtml @@ -83,7 +83,7 @@ @text } - @Html.DateTime(commentEntry.Comment.CommentDateUtc.GetValueOrDefault()) + @Display.DateTime(DateTimeUtc: commentEntry.Comment.CommentDateUtc.GetValueOrDefault())
  • diff --git a/src/Orchard.Web/Modules/Orchard.Comments/Views/Admin/Index.cshtml b/src/Orchard.Web/Modules/Orchard.Comments/Views/Admin/Index.cshtml index 1c198ee97..3e8fba850 100644 --- a/src/Orchard.Web/Modules/Orchard.Comments/Views/Admin/Index.cshtml +++ b/src/Orchard.Web/Modules/Orchard.Comments/Views/Admin/Index.cshtml @@ -77,7 +77,7 @@ @* would ideally have permalinks for individual comments *@ -

    +

    @if (commentEntry.Comment.CommentText != null) { var ellipsized = Html.Ellipsize(commentEntry.Comment.CommentText, 500); var paragraphed = new HtmlString(ellipsized.ToHtmlString().Replace("\r\n", "

    ")); diff --git a/src/Orchard.Web/Modules/Orchard.PublishLater/Views/Parts/PublishLater.Metadata.SummaryAdmin.cshtml b/src/Orchard.Web/Modules/Orchard.PublishLater/Views/Parts/PublishLater.Metadata.SummaryAdmin.cshtml index 1eb13bfe2..4ed1d302e 100644 --- a/src/Orchard.Web/Modules/Orchard.PublishLater/Views/Parts/PublishLater.Metadata.SummaryAdmin.cshtml +++ b/src/Orchard.Web/Modules/Orchard.PublishLater/Views/Parts/PublishLater.Metadata.SummaryAdmin.cshtml @@ -30,7 +30,7 @@ } else { @T( @T("Scheduled") - @Html.DateTime(((DateTime?)Model.ScheduledPublishUtc).Value.ToLocalTime(), T("M/d/yyyy h:mm tt")) + @Display.DateTime(((DateTime?)Model.ScheduledPublishUtc).Value.ToLocalTime(), T("M/d/yyyy h:mm tt")) } | 

  • }
\ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Setup/SetupMode.cs b/src/Orchard.Web/Modules/Orchard.Setup/SetupMode.cs index 4db4bc375..e33154734 100644 --- a/src/Orchard.Web/Modules/Orchard.Setup/SetupMode.cs +++ b/src/Orchard.Web/Modules/Orchard.Setup/SetupMode.cs @@ -188,6 +188,10 @@ namespace Orchard.Setup { public string BaseUrl { get { return ""; } } + + public string SiteTimeZone { + get { return TimeZoneInfo.Local.Id; } + } } } } diff --git a/src/Orchard/Mvc/Html/HtmlHelperExtensions.cs b/src/Orchard/Mvc/Html/HtmlHelperExtensions.cs index 9750c0c7c..90d4140f5 100644 --- a/src/Orchard/Mvc/Html/HtmlHelperExtensions.cs +++ b/src/Orchard/Mvc/Html/HtmlHelperExtensions.cs @@ -165,28 +165,6 @@ namespace Orchard.Mvc.Html { #endregion - #region Format Date/Time - - public static LocalizedString DateTime(this HtmlHelper htmlHelper, DateTime? value, LocalizedString defaultIfNull) { - return value.HasValue ? htmlHelper.DateTime(value.Value) : defaultIfNull; - } - - public static LocalizedString DateTime(this HtmlHelper htmlHelper, DateTime? value, LocalizedString defaultIfNull, LocalizedString customFormat) { - return value.HasValue ? htmlHelper.DateTime(value.Value, customFormat) : defaultIfNull; - } - - public static LocalizedString DateTime(this HtmlHelper htmlHelper, DateTime value) { - //TODO: (erikpo) This default format should come from a site setting - return htmlHelper.DateTime(value.ToLocalTime(), new LocalizedString("MMM d yyyy h:mm tt")); //todo: above comment and get rid of just wrapping this as a localized string - } - - public static LocalizedString DateTime(this HtmlHelper htmlHelper, DateTime value, LocalizedString customFormat) { - //TODO: (erikpo) In the future, convert this to "local" time before calling ToString - return new LocalizedString(value.ToString(customFormat.Text)); - } - - #endregion - #region Image public static MvcHtmlString Image(this HtmlHelper htmlHelper, string src, string alt, object htmlAttributes) { diff --git a/src/Orchard/Orchard.Framework.csproj b/src/Orchard/Orchard.Framework.csproj index 30d07d3c2..d42e28f4e 100644 --- a/src/Orchard/Orchard.Framework.csproj +++ b/src/Orchard/Orchard.Framework.csproj @@ -276,10 +276,16 @@ + + + + + + @@ -884,7 +890,7 @@ - + diff --git a/src/Orchard/Services/Clock.cs b/src/Orchard/Services/Clock.cs index e72f7fae1..692768617 100644 --- a/src/Orchard/Services/Clock.cs +++ b/src/Orchard/Services/Clock.cs @@ -2,20 +2,6 @@ using Orchard.Caching; namespace Orchard.Services { - public interface IClock : IVolatileProvider { - DateTime UtcNow { get; } - - /// - /// Each retrieved value is cached during the specified amount of time. - /// - IVolatileToken When(TimeSpan duration); - - /// - /// The cache is active until the specified time. Each subsequent access won't be cached. - /// - IVolatileToken WhenUtc(DateTime absoluteUtc); - } - public class Clock : IClock { public DateTime UtcNow { get { return DateTime.UtcNow; } diff --git a/src/Orchard/Services/IClock.cs b/src/Orchard/Services/IClock.cs new file mode 100644 index 000000000..3560a9318 --- /dev/null +++ b/src/Orchard/Services/IClock.cs @@ -0,0 +1,56 @@ +using System; +using Orchard.Caching; + +namespace Orchard.Services { + /// + /// Provides the current Utc , and time related method for cache management. + /// This service should be used whenever the current date and time are needed, instead of directly. + /// It also makes implementations more testable, as time can be mocked. + /// + public interface IClock : IVolatileProvider { + /// + /// Gets the current of the system, expressed in Utc + /// + DateTime UtcNow { get; } + + /// + /// Provides a instance which can be used to cache some information for a + /// specific duration. + /// + /// The duration that the token must be valid. + /// + /// This sample shows how to use the method by returning the result of + /// a method named LoadVotes(), which is computed every 10 minutes only. + /// + /// _cacheManager.Get("votes", + /// ctx => { + /// ctx.Monitor(_clock.When(TimeSpan.FromMinutes(10))); + /// return LoadVotes(); + /// }); + /// + /// + IVolatileToken When(TimeSpan duration); + + /// + /// Provides a instance which can be used to cache some + /// until a specific date and time. + /// + /// The date and time that the token must be valid until. + /// + /// This sample shows how to use the method by returning the result of + /// a method named LoadVotes(), which is computed once, and no more until the end of the year. + /// + /// var endOfYear = _clock.UtcNow; + /// endOfYear.Month = 12; + /// endOfYear.Day = 31; + /// + /// _cacheManager.Get("votes", + /// ctx => { + /// ctx.Monitor(_clock.WhenUtc(endOfYear)); + /// return LoadVotes(); + /// }); + /// + /// + IVolatileToken WhenUtc(DateTime absoluteUtc); + } +} diff --git a/src/Orchard/Settings/ISite.cs b/src/Orchard/Settings/ISite.cs index e37d47e63..666a58f46 100644 --- a/src/Orchard/Settings/ISite.cs +++ b/src/Orchard/Settings/ISite.cs @@ -1,4 +1,5 @@ -using Orchard.ContentManagement; +using System; +using Orchard.ContentManagement; namespace Orchard.Settings { /// @@ -14,5 +15,6 @@ namespace Orchard.Settings { ResourceDebugMode ResourceDebugMode { get; set; } int PageSize { get; set; } string BaseUrl { get; } + string SiteTimeZone { get; } } } diff --git a/src/Orchard/Time/CurrentTimeZoneWorkContext.cs b/src/Orchard/Time/CurrentTimeZoneWorkContext.cs new file mode 100644 index 000000000..8951ea2b7 --- /dev/null +++ b/src/Orchard/Time/CurrentTimeZoneWorkContext.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; + +namespace Orchard.Time { + public class CurrentTimeZoneWorkContext : IWorkContextStateProvider { + private readonly IEnumerable _timeZoneSelectors; + + public CurrentTimeZoneWorkContext(IEnumerable timeZoneSelectors) { + _timeZoneSelectors = timeZoneSelectors; + } + + public Func Get(string name) { + if (name == "CurrentTimeZone") { + return ctx => (T)(object)CurrentTimeZone(ctx.HttpContext); + } + return null; + } + + TimeZoneInfo CurrentTimeZone(HttpContextBase httpContext) { + var timeZone = _timeZoneSelectors + .Select(x => x.GetTimeZone(httpContext)) + .Where(x => x != null) + .OrderByDescending(x => x.Priority) + .FirstOrDefault(); + + if (timeZone == null) { + return null; + } + + return timeZone.TimeZone; + } + } +} diff --git a/src/Orchard/Time/FallbackTimeZoneSelector.cs b/src/Orchard/Time/FallbackTimeZoneSelector.cs new file mode 100644 index 000000000..180bb3e37 --- /dev/null +++ b/src/Orchard/Time/FallbackTimeZoneSelector.cs @@ -0,0 +1,16 @@ +using System; +using System.Web; + +namespace Orchard.Time { + /// + /// Implements by providing the timezone defined in the machine's local settings. + /// + public class FallbackTimeZoneSelector : ITimeZoneSelector { + public TimeZoneSelectorResult GetTimeZone(HttpContextBase context) { + return new TimeZoneSelectorResult { + Priority = -100, + TimeZone = TimeZoneInfo.Local + }; + } + } +} \ No newline at end of file diff --git a/src/Orchard/Time/ITimeZoneSelector.cs b/src/Orchard/Time/ITimeZoneSelector.cs new file mode 100644 index 000000000..ce341ecda --- /dev/null +++ b/src/Orchard/Time/ITimeZoneSelector.cs @@ -0,0 +1,7 @@ +using System.Web; + +namespace Orchard.Time { + public interface ITimeZoneSelector : IDependency { + TimeZoneSelectorResult GetTimeZone(HttpContextBase context); + } +} diff --git a/src/Orchard/Time/SiteTimeZoneSelector.cs b/src/Orchard/Time/SiteTimeZoneSelector.cs new file mode 100644 index 000000000..300d5548e --- /dev/null +++ b/src/Orchard/Time/SiteTimeZoneSelector.cs @@ -0,0 +1,28 @@ +using System; +using System.Web; + +namespace Orchard.Time { + /// + /// Implements by providing the timezone defined in the sites settings. + /// + public class SiteTimeZoneSelector : ITimeZoneSelector { + private readonly IWorkContextAccessor _workContextAccessor; + + public SiteTimeZoneSelector(IWorkContextAccessor workContextAccessor) { + _workContextAccessor = workContextAccessor; + } + + public TimeZoneSelectorResult GetTimeZone(HttpContextBase context) { + var siteTimeZoneId = _workContextAccessor.GetContext(context).CurrentSite.SiteTimeZone; + + if (String.IsNullOrEmpty(siteTimeZoneId)) { + return null; + } + + return new TimeZoneSelectorResult { + Priority = -5, + TimeZone = TimeZoneInfo.FindSystemTimeZoneById(siteTimeZoneId) + }; + } + } +} diff --git a/src/Orchard/Time/TimeZoneSelectorResult.cs b/src/Orchard/Time/TimeZoneSelectorResult.cs new file mode 100644 index 000000000..3a34731a0 --- /dev/null +++ b/src/Orchard/Time/TimeZoneSelectorResult.cs @@ -0,0 +1,8 @@ +using System; + +namespace Orchard.Time { + public class TimeZoneSelectorResult { + public int Priority { get; set; } + public TimeZoneInfo TimeZone { get; set; } + } +} diff --git a/src/Orchard/WorkContext.cs b/src/Orchard/WorkContext.cs index e5b5f1540..28910debe 100644 --- a/src/Orchard/WorkContext.cs +++ b/src/Orchard/WorkContext.cs @@ -1,4 +1,5 @@ -using System.Web; +using System; +using System.Web; using Orchard.Environment.Extensions.Models; using Orchard.Security; using Orchard.Settings; @@ -75,5 +76,13 @@ namespace Orchard { get { return GetState("CurrentCulture"); } set { SetState("CurrentCulture", value); } } + + /// + /// Time zone of the work context + /// + public TimeZoneInfo CurrentTimeZone { + get { return GetState("CurrentTimeZone"); } + set { SetState("CurrentTimeZone", value); } + } } }