From f019392a93842f123f55b8c5a6e91e856c89f911 Mon Sep 17 00:00:00 2001 From: Daniel Stolt Date: Sat, 2 Aug 2014 21:49:41 +0200 Subject: [PATCH] Changed implementation of DateTokens to use localization abstractions. Added more tokens. Beefed up unit tests to cover all tokens. --- .../Helpers/DateTimeExtensions.cs | 26 ++--- .../Services/AuditTrailManager.cs | 2 +- .../Services/CommonAuditTrailEventHandler.cs | 11 +- .../Orchard.Tokens/Providers/DateTokens.cs | 50 +++++--- .../Orchard.Tokens/Tests/DateTokenTests.cs | 108 +++++++++++++++--- .../Tests/StubWorkContextAccessor.cs | 2 +- .../Services/IDateLocalizationServices.txt | 63 ++-------- 7 files changed, 153 insertions(+), 109 deletions(-) diff --git a/src/Orchard.Web/Modules/Orchard.AuditTrail/Helpers/DateTimeExtensions.cs b/src/Orchard.Web/Modules/Orchard.AuditTrail/Helpers/DateTimeExtensions.cs index ad5eb75a9..43a7a5c6d 100644 --- a/src/Orchard.Web/Modules/Orchard.AuditTrail/Helpers/DateTimeExtensions.cs +++ b/src/Orchard.Web/Modules/Orchard.AuditTrail/Helpers/DateTimeExtensions.cs @@ -2,27 +2,27 @@ namespace Orchard.AuditTrail.Helpers { public static class DateTimeExtensions { - public static DateTime? Earliest(this DateTime? value) { - if (value == null) - return null; - - return Earliest(value.Value); - } - - public static DateTime Earliest(this DateTime value) { + public static DateTime StartOfDay(this DateTime value) { return new DateTime(value.Year, value.Month, value.Day, 0, 0, 0, 0, value.Kind); } - public static DateTime? Latest(this DateTime? value) { + public static DateTime? StartOfDay(this DateTime? value) { + if (value == null) + return null; + + return StartOfDay(value.Value); + } + + public static DateTime EndOfDay(this DateTime value) { + return new DateTime(value.Year, value.Month, value.Day, 23, 59, 59, 999, value.Kind); + } + + public static DateTime? EndOfDay(this DateTime? value) { if (value == null) return null; var v = value.Value; return new DateTime(v.Year, v.Month, v.Day, 23, 59, 59, 999, v.Kind); } - - public static DateTime Latest(this DateTime value) { - return new DateTime(value.Year, value.Month, value.Day, 23, 59, 59, 999, value.Kind); - } } } \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.AuditTrail/Services/AuditTrailManager.cs b/src/Orchard.Web/Modules/Orchard.AuditTrail/Services/AuditTrailManager.cs index 048d50363..ba56acd78 100644 --- a/src/Orchard.Web/Modules/Orchard.AuditTrail/Services/AuditTrailManager.cs +++ b/src/Orchard.Web/Modules/Orchard.AuditTrail/Services/AuditTrailManager.cs @@ -206,7 +206,7 @@ namespace Orchard.AuditTrail.Services { } public IEnumerable Trim(TimeSpan retentionPeriod) { - var dateThreshold = (_clock.UtcNow.Latest() - retentionPeriod); + var dateThreshold = (_clock.UtcNow.EndOfDay() - retentionPeriod); var query = _auditTrailRepository.Table.Where(x => x.CreatedUtc <= dateThreshold); var records = query.ToArray(); diff --git a/src/Orchard.Web/Modules/Orchard.AuditTrail/Services/CommonAuditTrailEventHandler.cs b/src/Orchard.Web/Modules/Orchard.AuditTrail/Services/CommonAuditTrailEventHandler.cs index 9df7d536f..694b7e9c9 100644 --- a/src/Orchard.Web/Modules/Orchard.AuditTrail/Services/CommonAuditTrailEventHandler.cs +++ b/src/Orchard.Web/Modules/Orchard.AuditTrail/Services/CommonAuditTrailEventHandler.cs @@ -18,8 +18,8 @@ namespace Orchard.AuditTrail.Services { public override void Filter(QueryFilterContext context) { var userName = context.Filters.Get("username"); var category = context.Filters.Get("category"); - var from = GetDateFromFilter(context.Filters, "From", "from").Earliest(); - var to = GetDateFromFilter(context.Filters, "To", "to").Latest(); + var from = GetDateFromFilter(context.Filters, "From", "from").StartOfDay(); + var to = GetDateFromFilter(context.Filters, "To", "to").EndOfDay(); var query = context.Query; if (!String.IsNullOrWhiteSpace(userName)) { @@ -57,13 +57,12 @@ namespace Orchard.AuditTrail.Services { } private DateTime? GetDateFromFilter(Filters filters, string fieldName, string prefix) { + var dateString = filters.Get(prefix + ".Date"); try { - var dateString = filters.Get(prefix + ".Date"); - var timeString = filters.Get(prefix + ".Time"); - return _dateLocalizationServices.ConvertFromLocalizedString(dateString, timeString); + return _dateLocalizationServices.ConvertFromLocalizedDateString(dateString); } catch (FormatException ex) { - filters.UpdateModel.AddModelError(prefix, T(@"Error parsing ""{0}"" date: {1}", fieldName, ex.Message)); + filters.UpdateModel.AddModelError(prefix, T(@"Error parsing '{0}' date string '{1}': {2}", fieldName, dateString, ex.Message)); return null; } } diff --git a/src/Orchard.Web/Modules/Orchard.Tokens/Providers/DateTokens.cs b/src/Orchard.Web/Modules/Orchard.Tokens/Providers/DateTokens.cs index bfd0afc67..bff088347 100644 --- a/src/Orchard.Web/Modules/Orchard.Tokens/Providers/DateTokens.cs +++ b/src/Orchard.Web/Modules/Orchard.Tokens/Providers/DateTokens.cs @@ -1,6 +1,7 @@ using System; using System.Globalization; using Orchard.Localization; +using Orchard.Localization.Models; using Orchard.Localization.Services; using Orchard.Mvc.Html; using Orchard.Services; @@ -8,66 +9,79 @@ using Orchard.Services; namespace Orchard.Tokens.Providers { public class DateTokens : ITokenProvider { private readonly IClock _clock; - private readonly IDateTimeFormatProvider _dateTimeLocalization; private readonly IWorkContextAccessor _workContextAccessor; - private readonly Lazy _cultureInfo; + private readonly IDateTimeFormatProvider _dateTimeFormats; + private readonly IDateFormatter _dateFormatter; private readonly IDateLocalizationServices _dateLocalizationServices; + //private readonly Lazy _cultureInfo; + public DateTokens( IClock clock, - IDateTimeFormatProvider dateTimeLocalization, IWorkContextAccessor workContextAccessor, - IDateLocalizationServices dateServices) { + IDateTimeFormatProvider dateTimeFormats, + IDateFormatter dateFormatter, + IDateLocalizationServices dateLocalizationServices) { _clock = clock; - _dateTimeLocalization = dateTimeLocalization; _workContextAccessor = workContextAccessor; - _cultureInfo = new Lazy(() => CultureInfo.GetCultureInfo(_workContextAccessor.GetContext().CurrentCulture)); - _dateLocalizationServices = dateServices; + _dateTimeFormats = dateTimeFormats; + _dateFormatter = dateFormatter; + _dateLocalizationServices = dateLocalizationServices; + //_cultureInfo = new Lazy(() => CultureInfo.GetCultureInfo(_workContextAccessor.GetContext().CurrentCulture)); + T = NullLocalizer.Instance; } public Localizer T { get; set; } public void Describe(DescribeContext context) { - context.For("Date", T("Date/time"), T("Current date/time tokens")) + context.For("Date", T("Date/Time"), T("Current date/time tokens")) .Token("Since", T("Since"), T("Relative to the current date/time."), "Date") .Token("Local", T("Local"), T("Based on the configured time zone and calendar."), "Date") + .Token("Short", T("Short Date and Time"), T("Short date and time format.")) .Token("ShortDate", T("Short Date"), T("Short date format.")) .Token("ShortTime", T("Short Time"), T("Short time format.")) .Token("Long", T("Long Date and Time"), T("Long date and time format.")) - .Token("Format:*", T("Format:"), T("Optional format specifier (e.g. yyyy/MM/dd). See format strings at Standard Formats and Custom Formats"), "DateTime"); + .Token("LongDate", T("Long Date"), T("Long date format.")) + .Token("LongTime", T("Long Time"), T("Long time format.")) + .Token("Format:*", T("Format:"), T("Optional custom date/time format string (e.g. yyyy/MM/dd). For reference see Custom Date and Time Format Strings"), "DateTime"); } public void Evaluate(EvaluateContext context) { context.For("Date", () => _clock.UtcNow) // {Date.Since} .Token("Since", DateTimeRelative) - .Chain("Since", "Date", DateTimeRelative) // {Date.Local} - .Token("Local", d => _dateLocalizationServices.ConvertToSiteTimeZone(d)) + .Token("Local", d => _dateLocalizationServices.ConvertToLocalizedString(d)) .Chain("Local", "Date", d => _dateLocalizationServices.ConvertToSiteTimeZone(d)) + // {Date.Short} + .Token("Short", d => _dateLocalizationServices.ConvertToLocalizedString(d, _dateTimeFormats.ShortDateTimeFormat, new DateLocalizationOptions() { EnableTimeZoneConversion = false })) // {Date.ShortDate} - .Token("ShortDate", d => d.ToString(_dateTimeLocalization.ShortDateFormat, _cultureInfo.Value)) + .Token("ShortDate", d => _dateLocalizationServices.ConvertToLocalizedString(d, _dateTimeFormats.ShortDateFormat, new DateLocalizationOptions() { EnableTimeZoneConversion = false })) // {Date.ShortTime} - .Token("ShortTime", d => d.ToString(_dateTimeLocalization.ShortTimeFormat, _cultureInfo.Value)) + .Token("ShortTime", d => _dateLocalizationServices.ConvertToLocalizedString(d, _dateTimeFormats.ShortTimeFormat, new DateLocalizationOptions() { EnableTimeZoneConversion = false })) // {Date.Long} - .Token("Long", d => d.ToString(_dateTimeLocalization.LongDateTimeFormat, _cultureInfo.Value)) + .Token("Long", d => _dateLocalizationServices.ConvertToLocalizedString(d, _dateTimeFormats.LongDateTimeFormat, new DateLocalizationOptions() { EnableTimeZoneConversion = false })) + // {Date.LongDate} + .Token("LongDate", d => _dateLocalizationServices.ConvertToLocalizedString(d, _dateTimeFormats.LongDateFormat, new DateLocalizationOptions() { EnableTimeZoneConversion = false })) + // {Date.LongTime} + .Token("LongTime", d => _dateLocalizationServices.ConvertToLocalizedString(d, _dateTimeFormats.LongTimeFormat, new DateLocalizationOptions() { EnableTimeZoneConversion = false })) // {Date} .Token( token => token == String.Empty ? String.Empty : null, - (token, d) => d.ToString(_dateTimeLocalization.ShortDateFormat + " " + _dateTimeLocalization.ShortTimeFormat, _cultureInfo.Value)) - // {Date.Format:} + (token, d) => _dateLocalizationServices.ConvertToLocalizedString(d, new DateLocalizationOptions() { EnableTimeZoneConversion = false })) + // {Date.Format:} .Token( token => token.StartsWith("Format:", StringComparison.OrdinalIgnoreCase) ? token.Substring("Format:".Length) : null, - (token, d) => d.ToString(token, _cultureInfo.Value)); + (token, d) => _dateLocalizationServices.ConvertToLocalizedString(d, token, new DateLocalizationOptions() { EnableTimeZoneConversion = false })); } private string DateTimeRelative(DateTime dateTimeUtc) { var time = _clock.UtcNow - dateTimeUtc.ToUniversalTime(); if (time.TotalDays > 7) - return dateTimeUtc.ToString(T("'on' MMM d yyyy 'at' h:mm tt").ToString(), _cultureInfo.Value); + return _dateFormatter.FormatDateTime(DateTimeParts.FromDateTime(dateTimeUtc), T("'on' MMM d yyyy 'at' h:mm tt").ToString()); 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.Web/Modules/Orchard.Tokens/Tests/DateTokenTests.cs b/src/Orchard.Web/Modules/Orchard.Tokens/Tests/DateTokenTests.cs index 1a675df05..8f5a03605 100644 --- a/src/Orchard.Web/Modules/Orchard.Tokens/Tests/DateTokenTests.cs +++ b/src/Orchard.Web/Modules/Orchard.Tokens/Tests/DateTokenTests.cs @@ -6,6 +6,7 @@ using Orchard.Localization.Services; using Orchard.Services; using Orchard.Tokens.Implementation; using Orchard.Tokens.Providers; +using Orchard.Localization.Models; namespace Orchard.Tokens.Tests { [TestFixture] @@ -13,6 +14,9 @@ namespace Orchard.Tokens.Tests { private IContainer _container; private ITokenizer _tokenizer; private IClock _clock; + private IDateTimeFormatProvider _dateTimeFormats; + private IDateLocalizationServices _dateLocalizationServices; + private IDateFormatter _dateFormatter; [SetUp] public void Init() { @@ -22,39 +26,107 @@ namespace Orchard.Tokens.Tests { builder.RegisterType().As(); builder.RegisterType().As(); builder.RegisterType().As(); - builder.RegisterType().As(); - builder.RegisterType().As(); - builder.RegisterType().As(); builder.RegisterType().As(); builder.RegisterType().As(); builder.RegisterType().As(); + builder.RegisterType().As(); + builder.RegisterType().As(); + builder.RegisterType().As(); _container = builder.Build(); _tokenizer = _container.Resolve(); _clock = _container.Resolve(); + _dateTimeFormats = _container.Resolve(); + _dateLocalizationServices = _container.Resolve(); + _dateFormatter = _container.Resolve(); } [Test] - public void TestDateTokens() { - var dateTimeLocalization = _container.Resolve(); - var culture = CultureInfo.GetCultureInfo(_container.Resolve().WorkContext.CurrentCulture); - - var dateTimeFormat = dateTimeLocalization.ShortDateFormat + " " + dateTimeLocalization.ShortTimeFormat; - - Assert.That(_tokenizer.Replace("{Date}", null), Is.EqualTo(_clock.UtcNow.ToString(dateTimeFormat, culture))); - Assert.That(_tokenizer.Replace("{Date}", new { Date = new DateTime(1978, 11, 15, 0, 0, 0, DateTimeKind.Utc) }), Is.EqualTo(new DateTime(1978, 11, 15, 0, 0, 0, DateTimeKind.Utc).ToString(dateTimeFormat, culture))); + public void TestDate() { + Assert.That(_tokenizer.Replace("{Date}", null), Is.EqualTo(_dateLocalizationServices.ConvertToLocalizedString(_clock.UtcNow, new DateLocalizationOptions() { EnableTimeZoneConversion = false }))); + Assert.That(_tokenizer.Replace("{Date}", new { Date = new DateTime(1978, 11, 15, 0, 0, 0, DateTimeKind.Utc) }), Is.EqualTo(_dateLocalizationServices.ConvertToLocalizedString(new DateTime(1978, 11, 15, 0, 0, 0, DateTimeKind.Utc), new DateLocalizationOptions() { EnableTimeZoneConversion = false }))); } [Test] - public void TestFormat() { - Assert.That(_tokenizer.Replace("{Date.Format:yyyy}", null), Is.EqualTo(_clock.UtcNow.ToString("yyyy"))); - } - - [Test] - public void TestSince() { - var date = _clock.UtcNow.Subtract(TimeSpan.FromHours(25)); + public void TestDateSince() { + var date = _clock.UtcNow.AddHours(-25); Assert.That(_tokenizer.Replace("{Date.Since}", new { Date = date }), Is.EqualTo("1 day ago")); } + [Test] + public void TestDateShort() { + Assert.That(_tokenizer.Replace("{Date.Short}", null), Is.EqualTo(_dateLocalizationServices.ConvertToLocalizedString(_clock.UtcNow, _dateTimeFormats.ShortDateTimeFormat, new DateLocalizationOptions() { EnableTimeZoneConversion = false }))); + } + + [Test] + public void TestDateShortDate() { + Assert.That(_tokenizer.Replace("{Date.ShortDate}", null), Is.EqualTo(_dateLocalizationServices.ConvertToLocalizedString(_clock.UtcNow, _dateTimeFormats.ShortDateFormat, new DateLocalizationOptions() { EnableTimeZoneConversion = false }))); + } + + [Test] + public void TestDateShortTime() { + Assert.That(_tokenizer.Replace("{Date.ShortTime}", null), Is.EqualTo(_dateLocalizationServices.ConvertToLocalizedString(_clock.UtcNow, _dateTimeFormats.ShortTimeFormat, new DateLocalizationOptions() { EnableTimeZoneConversion = false }))); + } + + [Test] + public void TestDateLong() { + Assert.That(_tokenizer.Replace("{Date.Long}", null), Is.EqualTo(_dateLocalizationServices.ConvertToLocalizedString(_clock.UtcNow, _dateTimeFormats.LongDateTimeFormat, new DateLocalizationOptions() { EnableTimeZoneConversion = false }))); + } + + [Test] + public void TestDateLongDate() { + Assert.That(_tokenizer.Replace("{Date.LongDate}", null), Is.EqualTo(_dateLocalizationServices.ConvertToLocalizedString(_clock.UtcNow, _dateTimeFormats.LongDateFormat, new DateLocalizationOptions() { EnableTimeZoneConversion = false }))); + } + + [Test] + public void TestDateLongTime() { + Assert.That(_tokenizer.Replace("{Date.LongTime}", null), Is.EqualTo(_dateLocalizationServices.ConvertToLocalizedString(_clock.UtcNow, _dateTimeFormats.LongTimeFormat, new DateLocalizationOptions() { EnableTimeZoneConversion = false }))); + } + + [Test] + public void TestDateFormat() { + Assert.That(_tokenizer.Replace("{Date.Format:yyyyMMdd}", null), Is.EqualTo(_dateLocalizationServices.ConvertToLocalizedString(_clock.UtcNow, "yyyyMMdd", new DateLocalizationOptions() { EnableTimeZoneConversion = false }))); + } + + [Test] + public void TestDateLocal() { + Assert.That(_tokenizer.Replace("{Date.Local}", null), Is.EqualTo(_dateLocalizationServices.ConvertToLocalizedString(_clock.UtcNow))); + Assert.That(_tokenizer.Replace("{Date.Local}", new { Date = new DateTime(1978, 11, 15, 0, 0, 0, DateTimeKind.Utc) }), Is.EqualTo(_dateLocalizationServices.ConvertToLocalizedString(new DateTime(1978, 11, 15, 0, 0, 0, DateTimeKind.Utc)))); + } + + [Test] + public void TestDateLocalShort() { + Assert.That(_tokenizer.Replace("{Date.Local.Short}", null), Is.EqualTo(_dateLocalizationServices.ConvertToLocalizedString(_clock.UtcNow, _dateTimeFormats.ShortDateTimeFormat))); + } + + [Test] + public void TestDateLocalShortDate() { + Assert.That(_tokenizer.Replace("{Date.Local.ShortDate}", null), Is.EqualTo(_dateLocalizationServices.ConvertToLocalizedString(_clock.UtcNow, _dateTimeFormats.ShortDateFormat))); + } + + [Test] + public void TestDateLocalShortTime() { + Assert.That(_tokenizer.Replace("{Date.Local.ShortTime}", null), Is.EqualTo(_dateLocalizationServices.ConvertToLocalizedString(_clock.UtcNow, _dateTimeFormats.ShortTimeFormat))); + } + + [Test] + public void TestDateLocalLong() { + Assert.That(_tokenizer.Replace("{Date.Local.Long}", null), Is.EqualTo(_dateLocalizationServices.ConvertToLocalizedString(_clock.UtcNow, _dateTimeFormats.LongDateTimeFormat))); + } + + [Test] + public void TestDateLocalLongDate() { + Assert.That(_tokenizer.Replace("{Date.Local.LongDate}", null), Is.EqualTo(_dateLocalizationServices.ConvertToLocalizedString(_clock.UtcNow, _dateTimeFormats.LongDateFormat))); + } + + [Test] + public void TestDateLocalLongTime() { + Assert.That(_tokenizer.Replace("{Date.Local.LongTime}", null), Is.EqualTo(_dateLocalizationServices.ConvertToLocalizedString(_clock.UtcNow, _dateTimeFormats.LongTimeFormat))); + } + + [Test] + public void TestDateLocalFormat() { + Assert.That(_tokenizer.Replace("{Date.Local.Format:yyyyMMdd}", null), Is.EqualTo(_dateLocalizationServices.ConvertToLocalizedString(_clock.UtcNow, "yyyyMMdd"))); + } } } diff --git a/src/Orchard.Web/Modules/Orchard.Tokens/Tests/StubWorkContextAccessor.cs b/src/Orchard.Web/Modules/Orchard.Tokens/Tests/StubWorkContextAccessor.cs index 1d5896731..1a567ee8c 100644 --- a/src/Orchard.Web/Modules/Orchard.Tokens/Tests/StubWorkContextAccessor.cs +++ b/src/Orchard.Web/Modules/Orchard.Tokens/Tests/StubWorkContextAccessor.cs @@ -37,7 +37,7 @@ namespace Orchard.Tokens.Tests { _initMethod(this); } - _contextDictonary["CurrentTimeZone"] = TimeZoneInfo.Local; + _contextDictonary["CurrentTimeZone"] = TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time"); _contextDictonary["CurrentCulture"] = "en-US"; _contextDictonary["CurrentCalendar"] = null; } diff --git a/src/Orchard/Localization/Services/IDateLocalizationServices.txt b/src/Orchard/Localization/Services/IDateLocalizationServices.txt index 05e93ae04..98933257c 100644 --- a/src/Orchard/Localization/Services/IDateLocalizationServices.txt +++ b/src/Orchard/Localization/Services/IDateLocalizationServices.txt @@ -1,53 +1,12 @@ -DateTime? ConvertToSiteTimeZone(DateTime? date) -DateTime? ConvertFromSiteTimeZone(DateTime? date) - - Also provides overloads with non-nullable DateTime parameters for compatibility with existing callers. - - Does only time zone conversion. No calendar conversion. - -DateTimeParts? ConvertToSiteCalendar(DateTime? date) -DateTime? ConvertFromSiteCalendar(Parts? dateParts) - - Does only calendar conversion. No time zone conversion. - - Instantiates the site calendar and does "manual" conversion. - - Uses a custom storage structure DateTimeParts for non-gregorian representation. - -string ConvertToLocalizedDateString(DateTime? date, DateLocalizationOptions options = DateLocalizationOptions.Default) -string ConvertToLocalizedTimeString(DateTime? date, DateLocalizationOptions options = DateLocalizationOptions.Default) -string ConvertToLocalizedString(DateTime? date, DateLocalizationOptions options) -string ConvertToLocalizedString(DateTime? date, string format, DateLocalizationOptions options) - - Also provides overloads with non-nullable DateTime parameters for compatibility with existing callers. - - By default performs the full conversion (time zone, calendar and formatting). Can be overridden by DateLocalizationOptions. - - If NOT ConvertCalendar, uses DateTime.ToString() with an cloned CultureInfo using GregorianCalendar of type Localized. - - If ConvertCalendar and UNSUPPORTED non-default calendar, uses ConvertToSiteCalendar() and Mahsa's custom formatting. - - If ConvertCalendar and SUPPORTED non-default calendar, uses DateTime.ToString() with cloned CultureInfo using an optional calendar instance, which does both calendar conversion and formatting. - - Otherwise uses DateTime.ToString() with configured CultureInfo which does both calendar conversion and formatting. - -DateTime? ConvertFromLocalizedDateString(string dateString, DateLocalizationOptions options = DateLocalizationOptions.Default) -DateTime? ConvertFromLocalizedTimeString(string timeString, DateLocalizationOptions options = DateLocalizationOptions.Default) -DateTime? ConvertFromLocalizedString(string dateTimeString, DateLocalizationOptions options = DateLocalizationOptions.Default) -DateTime? ConvertFromLocalizedString(string dateString, string timeString, DateLocalizationOptions options = DateLocalizationOptions.Default) - - By default performs the full conversion (parsing, calendar and time zone). Can be overridden by DateLocalizationOptions. - - ConvertFromLocalizedDateString() returns a DateTime? with the time component set to 00:00:00. ConvertTimeZone is ignored. - - ConvertFromLocalizedTimeString() returns a DateTime? with the date component set to DateTime.MinValue. ConvertCalendar is ignored. - - If NOT ConvertCalendar, uses DateTime.Parse() with an cloned CultureInfo using GregorianCalendar of type Localized. - - If ConvertCalendar and UNSUPPORTED non-default calendar, uses Mahsa's custom parsing and ConvertToSiteCalendar(). - - If ConvertCalendar and SUPPORTED non-default calendar, uses DateTime.Parse() with cloned CultureInfo using an optional calendar instance, which does both parsing and calendar conversion. - - Otherwise uses DateTime.Parse() with configured CultureInfo which does both parsing and calendar conversion. - - ConvertFromLocalizedString() with dateString or timeString set to null is the same as ConvertFromLocalizedTimeString() and ConvertFromLocalizedDateString() respectively. - - Checks if the parsed string matches NullText, and if so returns null. - -struct DateLocalizationOptions { - bool ConvertTimeZone; - bool ConvertCalendar; - string NullText; -} - TODO: - * Test for proper handling of fraction (f) format specifier - suspect it does not work properly in current state - * Add formatting and parsing of time zone information (add timezone properties to TimeParts structure) - * Write unit tests for DefaultDateLocalizationServices - * Add warning message when saving unsupported combination in settings - * Add support for the different Gregorian calendar types - * Go over date conversion logic in audit trail filtering to make sure it's correct - * Go over date conversion logic in DateTokens to make sure it's correct - * Improve DateTimeField: - - Surface the field mode (date, time or both) - - Do not perform time-zone conversion in date-only mode + * Test for proper handling of fraction (f) format specifier - suspect it does not work properly in current state + * Add formatting and parsing of time zone information (add timezone properties to TimeParts structure) + * 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 + +BREAKING: + * DateTokens "Date.Format:" and "Date.Local.Format:" only supports custom date/time format strings. \ No newline at end of file