#18388, #18502: Fixing DateTime localization and time zones

Work Items: 18502, 18388

--HG--
branch : 1.x
This commit is contained in:
Sebastien Ros
2012-03-09 16:09:49 -08:00
parent 75eea6b901
commit 2a2d44c226
13 changed files with 268 additions and 32 deletions

View File

@@ -5,4 +5,4 @@ dab42f20280f46c25c6c78ef13870f9a063bd026 src/Orchard.Web/Modules/Orchard.TaskLea
661c71dfa0dc833f01aa5244227e9e9bd14d11f3 src/Orchard.Web/Modules/Orchard.Tokens
88a640948e19a1a9dd79b859856375e292d53f2f src/orchard.web/Modules/Orchard.Alias
480597bb5f37f5506eaa580b91d1d8daa1f83d29 src/orchard.web/Modules/Orchard.Projections
973c38f5c22119102df509f0f3209278a9721f71 src/orchard.web/modules/Orchard.Fields
4bd0508bbbed49bc73f064d65583283c4e3ab2d0 src/orchard.web/modules/Orchard.Fields

View File

@@ -3,17 +3,24 @@ using System.Globalization;
using Orchard.ContentManagement;
using Orchard.ContentManagement.Drivers;
using Orchard.Core.Common.Models;
using Orchard.Core.Shapes.Localization;
using Orchard.Localization;
namespace Orchard.Core.Common.DateEditor {
public class DateEditorDriver : ContentPartDriver<CommonPart> {
private const string DatePattern = "M/d/yyyy";
private const string TimePattern = "h:mm:ss tt";
private readonly IDateTimeLocalization _dateTimeLocalization;
private readonly Lazy<CultureInfo> _cultureInfo;
public DateEditorDriver(
IOrchardServices services) {
IOrchardServices services,
IDateTimeLocalization dateTimeLocalization) {
_dateTimeLocalization = dateTimeLocalization;
T = NullLocalizer.Instance;
Services = services;
// initializing the culture info lazy initializer
_cultureInfo = new Lazy<CultureInfo>(() => CultureInfo.GetCultureInfo(Services.WorkContext.CurrentCulture));
}
public Localizer T { get; set; }
@@ -52,8 +59,11 @@ namespace Orchard.Core.Common.DateEditor {
theDatesHaveNotBeenModified;
if (theEditorShouldBeBlank == false) {
model.CreatedDate = part.CreatedUtc.Value.ToLocalTime().ToString(DatePattern, CultureInfo.InvariantCulture);
model.CreatedTime = part.CreatedUtc.Value.ToLocalTime().ToString(TimePattern, CultureInfo.InvariantCulture);
// date and time are formatted using the same patterns as DateTimePicker is, preventing other cultures issues
var createdLocal = TimeZoneInfo.ConvertTimeFromUtc(part.CreatedUtc.Value, Services.WorkContext.CurrentTimeZone);
model.CreatedDate = createdLocal.ToString(_dateTimeLocalization.ShortDateFormat.Text);
model.CreatedTime = createdLocal.ToString(_dateTimeLocalization.ShortTimeFormat.Text);
}
}
@@ -62,11 +72,15 @@ namespace Orchard.Core.Common.DateEditor {
if (!string.IsNullOrWhiteSpace(model.CreatedDate) && !string.IsNullOrWhiteSpace(model.CreatedTime)) {
DateTime createdUtc;
string parseDateTime = String.Concat(model.CreatedDate, " ", model.CreatedTime);
// use an english culture as it is the one used by jQuery.datepicker by default
if (DateTime.TryParse(parseDateTime, CultureInfo.GetCultureInfo("en-US"), DateTimeStyles.AssumeLocal, out createdUtc)) {
part.CreatedUtc = createdUtc.ToUniversalTime();
string parseDateTime = String.Concat(model.CreatedDate, " ", model.CreatedTime);
var dateTimeFormat = _dateTimeLocalization.ShortDateFormat + " " + _dateTimeLocalization.ShortTimeFormat;
// use current culture
if (DateTime.TryParseExact(parseDateTime, dateTimeFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out createdUtc)) {
// the date time is entered locally for the configured timezone
part.CreatedUtc = TimeZoneInfo.ConvertTimeToUtc(createdUtc, Services.WorkContext.CurrentTimeZone);
}
else {
updater.AddModelError(Prefix, T("{0} is an invalid date and time", parseDateTime));

View File

@@ -15,6 +15,10 @@
@Html.EditorFor(m => DateEditor.CreatedTime)
</fieldset>
@using(Script.Foot()) {
@* generates the localization script *@
@Display(New.DatePickerLocalization())
<script type="text/javascript">
//<![CDATA[
$(function () {

View File

@@ -227,6 +227,7 @@
<Compile Include="Settings\Models\SiteSettingsPart.cs" />
<Compile Include="Settings\Services\SiteService.cs" />
<Compile Include="Settings\ViewModels\SiteSettingsPartViewModel.cs" />
<Compile Include="Shapes\Localization\DateTimeLocalization.cs" />
<Compile Include="Shapes\ResourceManifest.cs" />
<Compile Include="Shapes\CoreShapes.cs" />
<Compile Include="Shapes\DateTimeShapes.cs" />

View File

@@ -1,6 +1,7 @@
using System;
using System.Web;
using System.Web.Mvc;
using Orchard.Core.Shapes.Localization;
using Orchard.DisplayManagement;
using Orchard.Localization;
using Orchard.Mvc.Html;
@@ -9,13 +10,16 @@ using Orchard.Services;
namespace Orchard.Core.Shapes {
public class DateTimeShapes : IDependency {
private readonly IClock _clock;
private readonly IDateTimeLocalization _dateTimeLocalization;
private readonly IWorkContextAccessor _workContextAccessor;
public DateTimeShapes(
IClock clock,
IDateTimeLocalization dateTimeLocalization,
IWorkContextAccessor workContextAccessor
) {
_clock = clock;
_dateTimeLocalization = dateTimeLocalization;
_workContextAccessor = workContextAccessor;
T = NullLocalizer.Instance;
}
@@ -45,7 +49,7 @@ namespace Orchard.Core.Shapes {
//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 DateTime(DateTimeUtc, _dateTimeLocalization.LongDateTimeFormat);
}
return new MvcHtmlString(ConvertToDisplayTime(DateTimeUtc).ToString(CustomFormat.Text));

View File

@@ -0,0 +1,126 @@
using System;
using Orchard.Localization;
namespace Orchard.Core.Shapes.Localization {
public interface IDateTimeLocalization : IDependency {
LocalizedString MonthNames { get; }
LocalizedString MonthNamesShort { get; }
LocalizedString DayNames { get; }
LocalizedString DayNamesShort { get; }
LocalizedString DayNamesMin { get; }
LocalizedString ShortDateFormat { get; }
LocalizedString ShortTimeFormat { get; }
LocalizedString LongDateTimeFormat { get; }
/// <summary>
/// The first day of the week, Sun = 0, Mon = 1, ...
/// </summary>
int FirstDay { get; }
/// <summary>
/// True if right-to-left language, false if left-to-right
/// </summary>
bool IsRTL { get; }
/// <summary>
/// True if the year select precedes month, false for month then year
/// </summary>
bool ShowMonthAfterYear { get; }
/// <summary>
/// Additional text to append to the year in the month headers
/// </summary>
string YearSuffix { get; }
}
public class DateTimeLocalization : IDateTimeLocalization {
public Localizer T { get; set; }
public LocalizedString MonthNames {
get { return T("January, February, March, April, May, June, July, August, September, October, November, December"); }
}
public LocalizedString MonthNamesShort {
get { return T("Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec"); }
}
public LocalizedString DayNames {
get { return T("Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday"); }
}
public LocalizedString DayNamesShort {
get { return T("Sun, Mon, Tue, Wed, Thu, Fri, Sat"); }
}
public LocalizedString DayNamesMin {
get { return T("Su, Mo, Tu, We, Th, Fr, Sa"); }
}
public LocalizedString ShortDateFormat {
get { return T("M/d/yyyy"); }
}
public LocalizedString ShortTimeFormat {
get { return T("h:mm tt"); }
}
public LocalizedString LongDateTimeFormat {
get { return T("MMM d yyyy h:mm tt"); }
}
public int FirstDay {
get {
var firstDay = 1;
var t = T("firstDay: 1").Text;
var parts = t.Split(':');
if(parts.Length == 2) {
Int32.TryParse(parts[1], out firstDay);
}
return firstDay;
}
}
public bool IsRTL {
get {
var isRTL = false;
var t = T("isRTL: false").Text;
var parts = t.Split(':');
if (parts.Length == 2) {
Boolean.TryParse(parts[1], out isRTL);
}
return isRTL;
}
}
public bool ShowMonthAfterYear {
get {
var showMonthAfterYear = false;
var t = T("showMonthAfterYear: false").Text;
var parts = t.Split(':');
if (parts.Length == 2) {
Boolean.TryParse(parts[1], out showMonthAfterYear);
}
return showMonthAfterYear;
}
}
public string YearSuffix {
get {
var yearSuffix = String.Empty;
var t = T("yearSuffix: ").Text;
var parts = t.Split(':');
if (parts.Length == 2) {
return parts[1].Trim();
}
return yearSuffix;
}
}
}
}

View File

@@ -6,6 +6,7 @@ using Orchard.ArchiveLater.ViewModels;
using Orchard.ContentManagement;
using Orchard.ContentManagement.Drivers;
using Orchard.ContentManagement.Handlers;
using Orchard.Core.Shapes.Localization;
using Orchard.Localization;
using System.Globalization;
@@ -13,13 +14,14 @@ namespace Orchard.ArchiveLater.Drivers {
public class ArchiveLaterPartDriver : ContentPartDriver<ArchiveLaterPart> {
private const string TemplateName = "Parts/ArchiveLater";
private readonly IArchiveLaterService _archiveLaterService;
private const string DatePattern = "M/d/yyyy";
private const string TimePattern = "h:mm tt";
private readonly IDateTimeLocalization _dateTimeLocalization;
public ArchiveLaterPartDriver(
IOrchardServices services,
IArchiveLaterService archiveLaterService) {
IArchiveLaterService archiveLaterService,
IDateTimeLocalization dateTimeLocalization) {
_archiveLaterService = archiveLaterService;
_dateTimeLocalization = dateTimeLocalization;
T = NullLocalizer.Instance;
Services = services;
}
@@ -39,11 +41,14 @@ namespace Orchard.ArchiveLater.Drivers {
}
protected override DriverResult Editor(ArchiveLaterPart part, dynamic shapeHelper) {
var model = new ArchiveLaterViewModel(part) {ScheduledArchiveUtc = part.ScheduledArchiveUtc.Value};
var localDate = new Lazy<DateTime>(() => TimeZoneInfo.ConvertTimeFromUtc(part.ScheduledArchiveUtc.Value.Value, Services.WorkContext.CurrentTimeZone));
model.ArchiveLater = model.ScheduledArchiveUtc.HasValue;
model.ScheduledArchiveDate = model.ScheduledArchiveUtc.HasValue ? model.ScheduledArchiveUtc.Value.ToLocalTime().ToString(DatePattern, CultureInfo.InvariantCulture) : String.Empty;
model.ScheduledArchiveTime = model.ScheduledArchiveUtc.HasValue ? model.ScheduledArchiveUtc.Value.ToLocalTime().ToString(TimePattern, CultureInfo.InvariantCulture) : String.Empty;
var model = new ArchiveLaterViewModel(part) {
ScheduledArchiveUtc = part.ScheduledArchiveUtc.Value,
ArchiveLater = part.ScheduledArchiveUtc.Value.HasValue,
ScheduledArchiveDate = part.ScheduledArchiveUtc.Value.HasValue ? localDate.Value.ToString(_dateTimeLocalization.ShortDateFormat.Text) : String.Empty,
ScheduledArchiveTime = part.ScheduledArchiveUtc.Value.HasValue ? localDate.Value.ToString(_dateTimeLocalization.ShortTimeFormat.Text) : String.Empty
};
return ContentShape("Parts_ArchiveLater_Edit",
() => shapeHelper.EditorTemplate(TemplateName: TemplateName, Model: model, Prefix: Prefix));
@@ -56,10 +61,14 @@ namespace Orchard.ArchiveLater.Drivers {
if ( model.ArchiveLater ) {
DateTime scheduled;
var parseDateTime = String.Concat(model.ScheduledArchiveDate, " ", model.ScheduledArchiveTime);
var dateTimeFormat = _dateTimeLocalization.ShortDateFormat + " " + _dateTimeLocalization.ShortTimeFormat;
// use an english culture as it is the one used by jQuery.datepicker by default
if (DateTime.TryParse(parseDateTime, CultureInfo.GetCultureInfo("en-US"), DateTimeStyles.AssumeLocal, out scheduled)) {
model.ScheduledArchiveUtc = scheduled.ToUniversalTime();
if (DateTime.TryParseExact(parseDateTime, dateTimeFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out scheduled)) {
// the date time is entered locally for the configured timezone
var timeZone = Services.WorkContext.CurrentTimeZone;
model.ScheduledArchiveUtc = TimeZoneInfo.ConvertTimeToUtc(scheduled, timeZone);
_archiveLaterService.ArchiveLater(model.ContentItem, model.ScheduledArchiveUtc.HasValue ? model.ScheduledArchiveUtc.Value : DateTime.MaxValue);
}
else {

View File

@@ -4,7 +4,7 @@
<ul class="pageStatus">
<li>
<img class="icon" src="@Href("~/Modules/Orchard.ArchiveLater/Content/Admin/images/scheduled.gif")" alt="@T("Scheduled")" title="@T("The page is scheduled for archiving")" />@T("Unpublish on")
@Display.DateTime(DateTimeUtc: (DateTime)Model.ScheduledArchiveUtc.ToLocalTime(), CustomFormat: T("M/d/yyyy h:mm tt"))
@Display.DateTime(DateTimeUtc: (DateTime)Model.ScheduledArchiveUtc, CustomFormat: T("M/d/yyyy h:mm tt"))
&nbsp;&#124;&nbsp;
</li>
</ul>

View File

@@ -3,6 +3,7 @@ using System.Xml;
using Orchard.ContentManagement;
using Orchard.ContentManagement.Drivers;
using Orchard.ContentManagement.Handlers;
using Orchard.Core.Shapes.Localization;
using Orchard.Mvc;
using Orchard.PublishLater.Models;
using Orchard.PublishLater.Services;
@@ -17,19 +18,25 @@ namespace Orchard.PublishLater.Drivers {
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly IPublishLaterService _publishLaterService;
private readonly IClock _clock;
private const string DatePattern = "M/d/yyyy";
private const string TimePattern = "h:mm tt";
private readonly IDateTimeLocalization _dateTimeLocalization;
private readonly Lazy<CultureInfo> _cultureInfo;
public PublishLaterPartDriver(
IOrchardServices services,
IHttpContextAccessor httpContextAccessor,
IPublishLaterService publishLaterService,
IClock clock) {
IClock clock,
IDateTimeLocalization dateTimeLocalization) {
_httpContextAccessor = httpContextAccessor;
_publishLaterService = publishLaterService;
_clock = clock;
_dateTimeLocalization = dateTimeLocalization;
T = NullLocalizer.Instance;
Services = services;
// initializing the culture info lazy initializer
_cultureInfo = new Lazy<CultureInfo>(() => CultureInfo.GetCultureInfo(Services.WorkContext.CurrentCulture));
}
public Localizer T { get; set; }
@@ -52,10 +59,11 @@ namespace Orchard.PublishLater.Drivers {
protected override DriverResult Editor(PublishLaterPart part, dynamic shapeHelper) {
// date and time are formatted using the same patterns as DateTimePicker is, preventing other cultures issues
var localDate = new Lazy<DateTime>( () => TimeZoneInfo.ConvertTimeFromUtc(part.ScheduledPublishUtc.Value.Value, Services.WorkContext.CurrentTimeZone));
var model = new PublishLaterViewModel(part) {
ScheduledPublishUtc = part.ScheduledPublishUtc.Value,
ScheduledPublishDate = part.ScheduledPublishUtc.Value.HasValue && !part.IsPublished() ? part.ScheduledPublishUtc.Value.Value.ToLocalTime().ToString(DatePattern, CultureInfo.InvariantCulture) : String.Empty,
ScheduledPublishTime = part.ScheduledPublishUtc.Value.HasValue && !part.IsPublished() ? part.ScheduledPublishUtc.Value.Value.ToLocalTime().ToString(TimePattern, CultureInfo.InvariantCulture) : String.Empty
ScheduledPublishDate = part.ScheduledPublishUtc.Value.HasValue && !part.IsPublished() ? localDate.Value.ToString(_dateTimeLocalization.ShortDateFormat.Text) : String.Empty,
ScheduledPublishTime = part.ScheduledPublishUtc.Value.HasValue && !part.IsPublished() ? localDate.Value.ToString(_dateTimeLocalization.ShortTimeFormat.Text) : String.Empty,
};
return ContentShape("Parts_PublishLater_Edit",
@@ -65,15 +73,22 @@ namespace Orchard.PublishLater.Drivers {
var model = new PublishLaterViewModel(part);
updater.TryUpdateModel(model, Prefix, null, null);
var httpContext = _httpContextAccessor.Current();
if (_httpContextAccessor.Current().Request.Form["submit.Save"] == "submit.PublishLater") {
if (httpContext.Request.Form["submit.Save"] == "submit.PublishLater") {
if (!string.IsNullOrWhiteSpace(model.ScheduledPublishDate) && !string.IsNullOrWhiteSpace(model.ScheduledPublishTime)) {
DateTime scheduled;
string parseDateTime = String.Concat(model.ScheduledPublishDate, " ", model.ScheduledPublishTime);
// use an english culture as it is the one used by jQuery.datepicker by default
if (DateTime.TryParse(parseDateTime, CultureInfo.GetCultureInfo("en-US"), DateTimeStyles.AssumeLocal, out scheduled)) {
model.ScheduledPublishUtc = part.ScheduledPublishUtc.Value = scheduled.ToUniversalTime();
string parseDateTime = String.Concat(model.ScheduledPublishDate, " ", model.ScheduledPublishTime);
var dateTimeFormat = _dateTimeLocalization.ShortDateFormat + " " + _dateTimeLocalization.ShortTimeFormat;
// use current culture
if (DateTime.TryParseExact(parseDateTime, dateTimeFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out scheduled)) {
// the date time is entered locally for the configured timezone
var timeZone = Services.WorkContext.CurrentTimeZone;
model.ScheduledPublishUtc = part.ScheduledPublishUtc.Value = TimeZoneInfo.ConvertTimeToUtc(scheduled, timeZone);
if (model.ScheduledPublishUtc < _clock.UtcNow) {
updater.AddModelError("ScheduledPublishUtcDate", T("You cannot schedule a publishing date in the past"));

View File

@@ -15,9 +15,14 @@
<button type="submit" name="submit.Save" value="submit.PublishLater">@T("Publish Later")</button>
</fieldset>
@using(Script.Foot()) {
@* generates the localization script *@
@Display(New.DatePickerLocalization())
<script type="text/javascript">
//<![CDATA[
$(function () {
var clearHint = function ($this) { if ($this.val() == $this.data("hint")) { $this.removeClass("hinted").val("") } };
var resetHint = function ($this) { setTimeout(function () { if (!$this.val()) { $this.addClass("hinted").val($this.data("hint")) } }, 300) };
@* todo: (heskew) make a plugin *@
@@ -33,6 +38,7 @@
$this.closest("form").submit(function() {clearHint(pickerInput); pickerInput = 0;});
}
});
$('#@ViewData.TemplateInfo.GetFullHtmlFieldId("ScheduledPublishDate")').datepicker({ showAnim: ""}).focus(function () { $('#@ViewData.TemplateInfo.GetFullHtmlFieldId("Command_PublishLater")').attr("checked", "checked") });
$('#@ViewData.TemplateInfo.GetFullHtmlFieldId("ScheduledPublishTime")').timepickr().focus(function () { $('#@ViewData.TemplateInfo.GetFullHtmlFieldId("Command_PublishLater")').attr("checked", "checked") });
})

View File

@@ -30,7 +30,7 @@
}
else {
<img class="icon" src="@Href("~/Modules/Orchard.PublishLater/Content/Admin/images/scheduled.gif")" alt="@T("Scheduled")" title="@T("The page is scheduled for publishing")" /><text> @T("Scheduled") </text>
@Display.DateTime(((DateTime?)Model.ScheduledPublishUtc).Value.ToLocalTime(), T("M/d/yyyy h:mm tt"))
@Display.DateTime(DateTimeUtc: ((DateTime?)Model.ScheduledPublishUtc).Value, CustomFormat: T("M/d/yyyy h:mm tt"))
}&nbsp;&#124;&nbsp;</li>
}
</ul>

View File

@@ -137,6 +137,10 @@
<Project>{2D1D92BB-4555-4CBE-8D0E-63563D6CE4C6}</Project>
<Name>Orchard.Framework</Name>
</ProjectReference>
<ProjectReference Include="..\..\Core\Orchard.Core.csproj">
<Project>{9916839C-39FC-4CEB-A5AF-89CA7E87119F}</Project>
<Name>Orchard.Core</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Content Include="Module.txt" />
@@ -156,6 +160,7 @@
</Content>
</ItemGroup>
<ItemGroup>
<None Include="Views\DatePickerLocalization.cshtml" />
<Content Include="web.config" />
</ItemGroup>
<ItemGroup>
@@ -163,6 +168,7 @@
<SubType>Designer</SubType>
</Content>
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\WebApplications\Microsoft.WebApplication.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.

View File

@@ -0,0 +1,51 @@
@using Orchard.Core.Shapes.Localization
@{
var dateTimeLocalization = WorkContext.Resolve<IDateTimeLocalization>();
var monthNames = FormatJsList(dateTimeLocalization.MonthNames.Text);
var monthNamesShort = FormatJsList(dateTimeLocalization.MonthNamesShort.Text);
var dayNames = FormatJsList(dateTimeLocalization.DayNames.Text);
var dayNamesShort = FormatJsList(dateTimeLocalization.DayNamesShort.Text);
var dayNamesMin = FormatJsList(dateTimeLocalization.DayNamesMin.Text);
// convert .NET format into jQuery format
// http://msdn.microsoft.com/en-us/library/8kb3ddd4.aspx
// http://docs.jquery.com/UI/Datepicker/formatDate
var dateFormat = dateTimeLocalization.ShortDateFormat.Text
.Replace("yyyy", "YY")
.Replace("yy", "Y")
.Replace("Y", "y")
.Replace("M", "m")
;
}
@functions {
private string FormatJsList(string csv) {
return "'" + String.Join("', '", csv.Split(',').Select(x => x.Trim())) + "'";
}
}
<script type="text/javascript">
//<![CDATA[
$(function() {
$.datepicker.regional[''] = {
closeText: '@T("Done")', // Display text for close link
prevText: '@T("Prev")', // Display text for previous month link
nextText: '@T("Next")', // Display text for next month link
currentText: '@T("Today")', // Display text for current month link
monthNames: [@Html.Raw(monthNames)], // Names of months for drop-down and formatting
monthNamesShort: [@Html.Raw(monthNamesShort)], // For formatting
dayNames: [@Html.Raw(dayNames)], // For formatting
dayNamesShort: [@Html.Raw(dayNamesShort)], // For formatting
dayNamesMin: [@Html.Raw(dayNamesMin)], // Column headings for days starting at Sunday
weekHeader: '@T("Wk")', // Column header for week of the year
dateFormat: '@dateFormat', // See format options on parseDate
firstDay: @dateTimeLocalization.FirstDay, // The first day of the week, Sun = 0, Mon = 1, ...
isRTL: @(dateTimeLocalization.IsRTL ? "true" : "false"), // True if right-to-left language, false if left-to-right
showMonthAfterYear: @(dateTimeLocalization.ShowMonthAfterYear ? "true" : "false"), // True if the year select precedes month, false for month then year
yearSuffix: '@dateTimeLocalization.YearSuffix' // Additional text to append to the year in the month headers
};
})
//]]>
</script>