mirror of
https://github.com/OrchardCMS/Orchard.git
synced 2025-10-15 19:54:57 +08:00
Improved DateTimeField (bypass time zone conversion if date-only, store/retrieve only the relevant components from the repository, surface the display mode as a property on the field).
This commit is contained in:
@@ -65,7 +65,7 @@ namespace Orchard.Core.Shapes {
|
||||
//using a LocalizedString forces the caller to use a localizable format
|
||||
|
||||
if (CustomFormat == null || String.IsNullOrWhiteSpace(CustomFormat.Text)) {
|
||||
return new MvcHtmlString(_dateLocalizationServices.ConvertToLocalizedString(DateTimeUtc, _dateTimeLocalization.LongDateTimeFormat));
|
||||
return new MvcHtmlString(_dateLocalizationServices.ConvertToLocalizedString(DateTimeUtc, _dateTimeLocalization.ShortDateTimeFormat));
|
||||
}
|
||||
|
||||
return new MvcHtmlString(_dateLocalizationServices.ConvertToLocalizedString(DateTimeUtc, CustomFormat.Text));
|
||||
|
@@ -10,7 +10,7 @@
|
||||
<section class="audittrail-event-metadata-section">
|
||||
@T("Event:") <strong>@descriptor.Name</strong><br />
|
||||
@T("Category:") <strong>@descriptor.CategoryDescriptor.Name</strong><br />
|
||||
@T("Timestamp:") <strong>@Display.DateTime(DateTimeUtc: record.CreatedUtc, CustomFormat: T("g"))</strong><br />
|
||||
@T("Timestamp:") <strong>@Display.DateTime(DateTimeUtc: record.CreatedUtc)</strong><br />
|
||||
@T("Username:") <strong>@record.UserName</strong>
|
||||
</section>
|
||||
<section class="audittrail-event-details-section">
|
||||
|
@@ -49,7 +49,7 @@
|
||||
<td class="category-column">@record.CategoryDescriptor.Name</td>
|
||||
<td class="event-column">@record.EventDescriptor.Name</td>
|
||||
<td class="user-column">@record.Record.UserName</td>
|
||||
<td class="timestamp-column">@Display.DateTime(DateTimeUtc: record.Record.CreatedUtc, CustomFormat: T("g"))</td>
|
||||
<td class="timestamp-column">@Display.DateTime(DateTimeUtc: record.Record.CreatedUtc)</td>
|
||||
<td class="summary-column">@Display(record.SummaryShape)</td>
|
||||
<td class="comment-column">@record.Record.Comment</td>
|
||||
<td class="actions-column">@Html.ActionLink(T("Details").Text, "Detail", "Admin", new { id = record.Record.Id, area = "Orchard.AuditTrail" }, null)</td>
|
||||
|
@@ -11,6 +11,7 @@ using Orchard.ContentManagement.Handlers;
|
||||
using Orchard.Localization;
|
||||
using Orchard.Localization.Services;
|
||||
using Orchard.Core.Common.ViewModels;
|
||||
using Orchard.Localization.Models;
|
||||
|
||||
namespace Orchard.Fields.Drivers {
|
||||
[UsedImplicitly]
|
||||
@@ -41,16 +42,25 @@ namespace Orchard.Fields.Drivers {
|
||||
() => {
|
||||
var settings = field.PartFieldDefinition.Settings.GetModel<DateTimeFieldSettings>();
|
||||
var value = field.DateTime;
|
||||
var options = new DateLocalizationOptions();
|
||||
|
||||
// Don't do any time zone conversion if field is semantically a date-only field, because that might mutate the date component.
|
||||
if (settings.Display == DateTimeFieldDisplays.DateOnly) {
|
||||
options.EnableTimeZoneConversion = false;
|
||||
}
|
||||
|
||||
var showDate = settings.Display == DateTimeFieldDisplays.DateAndTime || settings.Display == DateTimeFieldDisplays.DateOnly;
|
||||
var showTime = settings.Display == DateTimeFieldDisplays.DateAndTime || settings.Display == DateTimeFieldDisplays.TimeOnly;
|
||||
|
||||
var viewModel = new DateTimeFieldViewModel {
|
||||
Name = field.DisplayName,
|
||||
Hint = settings.Hint,
|
||||
IsRequired = settings.Required,
|
||||
Editor = new DateTimeEditor() {
|
||||
Date = DateLocalizationServices.ConvertToLocalizedDateString(value),
|
||||
Time = DateLocalizationServices.ConvertToLocalizedTimeString(value),
|
||||
ShowDate = settings.Display == DateTimeFieldDisplays.DateAndTime || settings.Display == DateTimeFieldDisplays.DateOnly,
|
||||
ShowTime = settings.Display == DateTimeFieldDisplays.DateAndTime || settings.Display == DateTimeFieldDisplays.TimeOnly,
|
||||
Date = showDate ? DateLocalizationServices.ConvertToLocalizedDateString(value, options) : null,
|
||||
Time = showTime ? DateLocalizationServices.ConvertToLocalizedTimeString(value, options) : null,
|
||||
ShowDate = showDate,
|
||||
ShowTime = showTime,
|
||||
}
|
||||
};
|
||||
|
||||
@@ -63,16 +73,25 @@ namespace Orchard.Fields.Drivers {
|
||||
protected override DriverResult Editor(ContentPart part, DateTimeField field, dynamic shapeHelper) {
|
||||
var settings = field.PartFieldDefinition.Settings.GetModel<DateTimeFieldSettings>();
|
||||
var value = field.DateTime;
|
||||
var options = new DateLocalizationOptions();
|
||||
|
||||
// Don't do any time zone conversion if field is semantically a date-only field, because that might mutate the date component.
|
||||
if (settings.Display == DateTimeFieldDisplays.DateOnly) {
|
||||
options.EnableTimeZoneConversion = false;
|
||||
}
|
||||
|
||||
var showDate = settings.Display == DateTimeFieldDisplays.DateAndTime || settings.Display == DateTimeFieldDisplays.DateOnly;
|
||||
var showTime = settings.Display == DateTimeFieldDisplays.DateAndTime || settings.Display == DateTimeFieldDisplays.TimeOnly;
|
||||
|
||||
var viewModel = new DateTimeFieldViewModel {
|
||||
Name = field.DisplayName,
|
||||
Hint = settings.Hint,
|
||||
IsRequired = settings.Required,
|
||||
Editor = new DateTimeEditor() {
|
||||
Date = DateLocalizationServices.ConvertToLocalizedDateString(value),
|
||||
Time = DateLocalizationServices.ConvertToLocalizedTimeString(value),
|
||||
ShowDate = settings.Display == DateTimeFieldDisplays.DateAndTime || settings.Display == DateTimeFieldDisplays.DateOnly,
|
||||
ShowTime = settings.Display == DateTimeFieldDisplays.DateAndTime || settings.Display == DateTimeFieldDisplays.TimeOnly,
|
||||
Date = showDate ? DateLocalizationServices.ConvertToLocalizedDateString(value, options) : null,
|
||||
Time = showTime ? DateLocalizationServices.ConvertToLocalizedTimeString(value, options) : null,
|
||||
ShowDate = showDate,
|
||||
ShowTime = showTime,
|
||||
}
|
||||
};
|
||||
|
||||
@@ -86,13 +105,34 @@ namespace Orchard.Fields.Drivers {
|
||||
if (updater.TryUpdateModel(viewModel, GetPrefix(field, part), null, null)) {
|
||||
|
||||
var settings = field.PartFieldDefinition.Settings.GetModel<DateTimeFieldSettings>();
|
||||
if (settings.Required && (((settings.Display == DateTimeFieldDisplays.DateAndTime || settings.Display == DateTimeFieldDisplays.DateOnly) && String.IsNullOrWhiteSpace(viewModel.Editor.Date)) || ((settings.Display == DateTimeFieldDisplays.DateAndTime || settings.Display == DateTimeFieldDisplays.TimeOnly) && String.IsNullOrWhiteSpace(viewModel.Editor.Time)))) {
|
||||
|
||||
var options = new DateLocalizationOptions();
|
||||
|
||||
// Don't do any time zone conversion if field is semantically a date-only field, because that might mutate the date component.
|
||||
if (settings.Display == DateTimeFieldDisplays.DateOnly) {
|
||||
options.EnableTimeZoneConversion = false;
|
||||
}
|
||||
|
||||
var showDate = settings.Display == DateTimeFieldDisplays.DateAndTime || settings.Display == DateTimeFieldDisplays.DateOnly;
|
||||
var showTime = settings.Display == DateTimeFieldDisplays.DateAndTime || settings.Display == DateTimeFieldDisplays.TimeOnly;
|
||||
|
||||
if (settings.Required && ((showDate && String.IsNullOrWhiteSpace(viewModel.Editor.Date)) || (showTime && String.IsNullOrWhiteSpace(viewModel.Editor.Time)))) {
|
||||
updater.AddModelError(GetPrefix(field, part), T("{0} is required.", field.DisplayName));
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
try {
|
||||
var utcDateTime = DateLocalizationServices.ConvertFromLocalizedString(viewModel.Editor.Date, viewModel.Editor.Time);
|
||||
var utcDateTime = DateLocalizationServices.ConvertFromLocalizedString(viewModel.Editor.Date, viewModel.Editor.Time, options);
|
||||
|
||||
if (utcDateTime.HasValue) {
|
||||
field.DateTime = utcDateTime.Value;
|
||||
// Hackish workaround to make sure a time-only field with an entered time equivalent to
|
||||
// 00:00 UTC doesn't get stored as a full DateTime.MinValue in the database, resulting
|
||||
// in it being interpreted as an empty value when subsequently retrieved.
|
||||
if (settings.Display == DateTimeFieldDisplays.TimeOnly && utcDateTime.Value == DateTime.MinValue) {
|
||||
field.DateTime = utcDateTime.Value.AddDays(1);
|
||||
}
|
||||
else {
|
||||
field.DateTime = utcDateTime.Value;
|
||||
}
|
||||
} else {
|
||||
field.DateTime = DateTime.MinValue;
|
||||
}
|
||||
|
@@ -1,17 +1,37 @@
|
||||
using System;
|
||||
using Orchard.ContentManagement;
|
||||
using Orchard.ContentManagement.FieldStorage;
|
||||
using Orchard.Fields.Settings;
|
||||
|
||||
namespace Orchard.Fields.Fields {
|
||||
public class DateTimeField : ContentField {
|
||||
|
||||
public DateTime DateTime {
|
||||
get {
|
||||
var settings = this.PartFieldDefinition.Settings.GetModel<DateTimeFieldSettings>();
|
||||
var value = Storage.Get<DateTime>();
|
||||
if (settings.Display == DateTimeFieldDisplays.DateOnly) {
|
||||
return new DateTime(value.Year, value.Month, value.Day);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
set { Storage.Set(value); }
|
||||
set {
|
||||
var settings = this.PartFieldDefinition.Settings.GetModel<DateTimeFieldSettings>();
|
||||
if (settings.Display == DateTimeFieldDisplays.DateOnly) {
|
||||
Storage.Set(new DateTime(value.Year, value.Month, value.Day));
|
||||
}
|
||||
else {
|
||||
Storage.Set(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public DateTimeFieldDisplays Display {
|
||||
get {
|
||||
var settings = this.PartFieldDefinition.Settings.GetModel<DateTimeFieldSettings>();
|
||||
return settings.Display;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -2,22 +2,26 @@
|
||||
using System.Globalization;
|
||||
using Orchard.ContentManagement;
|
||||
using Orchard.Localization.Models;
|
||||
using Orchard.Services;
|
||||
using Orchard.Settings;
|
||||
|
||||
namespace Orchard.Localization.Services {
|
||||
|
||||
public class DefaultDateLocalizationServices : IDateLocalizationServices {
|
||||
|
||||
private readonly IClock _clock;
|
||||
private readonly IWorkContextAccessor _workContextAccessor;
|
||||
private readonly IDateTimeFormatProvider _dateTimeFormatProvider;
|
||||
private readonly IDateFormatter _dateFormatter;
|
||||
private readonly ICalendarManager _calendarManager;
|
||||
|
||||
public DefaultDateLocalizationServices(
|
||||
IClock clock,
|
||||
IWorkContextAccessor workContextAccessor,
|
||||
IDateTimeFormatProvider dateTimeFormatProvider,
|
||||
IDateFormatter dateFormatter,
|
||||
ICalendarManager calendarManager) {
|
||||
_clock = clock;
|
||||
_workContextAccessor = workContextAccessor;
|
||||
_dateTimeFormatProvider = dateTimeFormatProvider;
|
||||
_dateFormatter = dateFormatter;
|
||||
@@ -63,7 +67,32 @@ namespace Orchard.Localization.Services {
|
||||
}
|
||||
|
||||
public string ConvertToLocalizedTimeString(DateTime? date, DateLocalizationOptions options = null) {
|
||||
return ConvertToLocalizedString(date, _dateTimeFormatProvider.LongTimeFormat, options);
|
||||
options = options ?? new DateLocalizationOptions();
|
||||
|
||||
if (!date.HasValue) {
|
||||
return options.NullText;
|
||||
}
|
||||
|
||||
var dateValue = date.Value;
|
||||
|
||||
if (options.EnableTimeZoneConversion) {
|
||||
// Since no date component is expected (technically the date component is that of DateTime.MinValue) then
|
||||
// we must employ some trickery, for two reasons:
|
||||
// * DST can be active or not dependeng on the time of the year. We want the conversion to always act as if the time represents today, but we don't want that date stored.
|
||||
// * 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 parts = DateTimeParts.FromDateTime(dateValue);
|
||||
if (options.EnableCalendarConversion && !(CurrentCalendar is GregorianCalendar)) {
|
||||
parts = ConvertToSiteCalendar(dateValue);
|
||||
}
|
||||
|
||||
return _dateFormatter.FormatDateTime(parts, _dateTimeFormatProvider.LongTimeFormat);
|
||||
}
|
||||
|
||||
public string ConvertToLocalizedString(DateTime date, DateLocalizationOptions options = null) {
|
||||
@@ -127,7 +156,19 @@ namespace Orchard.Localization.Services {
|
||||
}
|
||||
|
||||
if (hasTime && options.EnableTimeZoneConversion) {
|
||||
// If there is no date component (technically the date component is that of DateTime.MinValue) then
|
||||
// we must employ some trickery, for two reasons:
|
||||
// * DST can be active or not dependeng on the time of the year. We want the conversion to always act as if the time represents today, but we don't want that date stored.
|
||||
// * 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.
|
||||
if (!hasDate) {
|
||||
var now = _clock.UtcNow;
|
||||
dateValue = new DateTime(now.Year, now.Month, now.Day, dateValue.Hour, dateValue.Minute, dateValue.Second, dateValue.Millisecond, dateValue.Kind);
|
||||
}
|
||||
dateValue = ConvertFromSiteTimeZone(dateValue);
|
||||
if (!hasDate) {
|
||||
dateValue = new DateTime(DateTime.MinValue.Year, DateTime.MinValue.Month, DateTime.MinValue.Day, dateValue.Hour, dateValue.Minute, dateValue.Second, dateValue.Millisecond, dateValue.Kind);
|
||||
}
|
||||
}
|
||||
|
||||
return dateValue;
|
||||
|
@@ -4,9 +4,9 @@ TODO:
|
||||
* Write unit tests for DefaultDateLocalizationServices
|
||||
* Add warning message when saving unsupported combination in settings
|
||||
* Add support for the different Gregorian calendar types
|
||||
* Improve DateTimeField:
|
||||
- Surface the field mode (date, time or both)
|
||||
- Do not perform time-zone conversion in date-only mode
|
||||
* Make sure DateTimeField, ArchiveLaterPart and PublishLaterPart handle Persian dates correctly.
|
||||
* Improve CultureDateTimeFormatProvider to return correct information for fa-IR culture when PersianCalendar is in use.
|
||||
* Check for all uses of Display.DateTime that use a standard format string - no can do!
|
||||
|
||||
BREAKING:
|
||||
* DateTokens "Date.Format:<formatString>" and "Date.Local.Format:<formatString>" only supports custom date/time format strings.
|
Reference in New Issue
Block a user