mirror of
https://github.com/OrchardCMS/Orchard.git
synced 2025-10-15 19:54:57 +08:00
Added formatting and parsing support for time zone and offset information (the "K", "zzz", "zz" and "z" custom format specifiers) and unit tests to cover it.
This commit is contained in:
@@ -10,8 +10,8 @@ namespace Orchard.Framework.Tests.Localization {
|
||||
[Test]
|
||||
[Description("Equal instances return equality.")]
|
||||
public void EqualsTest01() {
|
||||
var target = new DateTimeParts(2014, 5, 31, 10, 0, 0, 0);
|
||||
var other = new DateTimeParts(2014, 5, 31, 10, 0, 0, 0);
|
||||
var target = new DateTimeParts(2014, 5, 31, 10, 0, 0, 0, DateTimeKind.Unspecified, offset: TimeSpan.Zero);
|
||||
var other = new DateTimeParts(2014, 5, 31, 10, 0, 0, 0, DateTimeKind.Unspecified, offset: TimeSpan.Zero);
|
||||
|
||||
var result = target.Equals(other);
|
||||
|
||||
@@ -21,8 +21,8 @@ namespace Orchard.Framework.Tests.Localization {
|
||||
[Test]
|
||||
[Description("Different instances do not return equality.")]
|
||||
public void EqualsTest02() {
|
||||
var target = new DateTimeParts(2014, 5, 31, 10, 0, 0, 0);
|
||||
var other = new DateTimeParts(2014, 5, 31, 10, 0, 0, 1);
|
||||
var target = new DateTimeParts(2014, 5, 31, 10, 0, 0, 0, DateTimeKind.Unspecified, offset: TimeSpan.Zero);
|
||||
var other = new DateTimeParts(2014, 5, 31, 10, 0, 0, 1, DateTimeKind.Unspecified, offset: TimeSpan.Zero);
|
||||
|
||||
var result = target.Equals(other);
|
||||
|
||||
|
@@ -36,14 +36,14 @@ 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");
|
||||
var container = InitializeContainer(culture.Name, "GregorianCalendar", TimeZoneInfo.Utc);
|
||||
var formats = container.Resolve<IDateTimeFormatProvider>();
|
||||
var target = container.Resolve<IDateFormatter>();
|
||||
|
||||
foreach (var dateTimeFormat in formats.AllDateTimeFormats) { // All date and time formats supported by the culture.
|
||||
for (var month = 1; month <= 12; month++) { // All months in the year.
|
||||
|
||||
DateTime dateTime = new DateTime(1998, month, 1, 10, 30, 30);
|
||||
DateTime dateTime = new DateTime(1998, month, 1, 10, 30, 30, DateTimeKind.Utc);
|
||||
|
||||
// Print string using Gregorian calendar to avoid calendar conversion.
|
||||
var cultureGregorian = (CultureInfo)culture.Clone();
|
||||
@@ -56,7 +56,7 @@ namespace Orchard.Framework.Tests.Localization {
|
||||
|
||||
try {
|
||||
var result = target.ParseDateTime(dateTimeString, dateTimeFormat);
|
||||
var expected = GetExpectedDateTimeParts(dateTime, dateTimeFormat);
|
||||
var expected = GetExpectedDateTimeParts(dateTime, dateTimeFormat, TimeZoneInfo.Utc);
|
||||
Assert.AreEqual(expected, result);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
@@ -85,14 +85,14 @@ 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");
|
||||
var container = InitializeContainer(culture.Name, "GregorianCalendar", TimeZoneInfo.Utc);
|
||||
var formats = container.Resolve<IDateTimeFormatProvider>();
|
||||
var target = container.Resolve<IDateFormatter>();
|
||||
|
||||
foreach (var dateTimeFormat in formats.AllDateTimeFormats) { // All date and time formats supported by the culture.
|
||||
foreach (var hour in new[] { 0, 6, 12, 18 }) { // Enough hours to cover all code paths (AM/PM, 12<->00, etc).
|
||||
|
||||
DateTime dateTime = new DateTime(1998, 1, 1, hour, 30, 30);
|
||||
DateTime dateTime = new DateTime(1998, 1, 1, hour, 30, 30, DateTimeKind.Utc);
|
||||
|
||||
// Print string using Gregorian calendar to avoid calendar conversion.
|
||||
var cultureGregorian = (CultureInfo)culture.Clone();
|
||||
@@ -105,7 +105,75 @@ namespace Orchard.Framework.Tests.Localization {
|
||||
|
||||
try {
|
||||
var result = target.ParseDateTime(dateTimeString, dateTimeFormat);
|
||||
var expected = GetExpectedDateTimeParts(dateTime, dateTimeFormat);
|
||||
var expected = GetExpectedDateTimeParts(dateTime, dateTimeFormat, TimeZoneInfo.Utc);
|
||||
Assert.AreEqual(expected, result);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
failedCases.TryAdd(caseKey, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (failedCases.Count > maxFailedCases) {
|
||||
throw new AggregateException(String.Format("Parse tests failed for {0} of {1} cases. Expected {2} failed cases or less.", failedCases.Count, allCases.Count, maxFailedCases), failedCases.Values);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Description("Date/time parsing works correctly for all combinations of kinds, time zones, format strings and cultures..")]
|
||||
public void ParseDateTimeTest03() {
|
||||
var allCases = new ConcurrentBag<string>();
|
||||
var failedCases = new ConcurrentDictionary<string, Exception>();
|
||||
var maxFailedCases = 0;
|
||||
|
||||
var options = new ParallelOptions();
|
||||
if (Debugger.IsAttached) {
|
||||
options.MaxDegreeOfParallelism = 1;
|
||||
}
|
||||
|
||||
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 formats = container.Resolve<IDateTimeFormatProvider>();
|
||||
var target = container.Resolve<IDateFormatter>();
|
||||
|
||||
foreach (var dateTimeFormat in formats.AllDateTimeFormats) { // All date and time formats supported by the culture.
|
||||
|
||||
var kind = DateTimeKind.Unspecified;
|
||||
var offset = timeZone.BaseUtcOffset;
|
||||
if (timeZone == TimeZoneInfo.Utc) {
|
||||
kind = DateTimeKind.Utc;
|
||||
}
|
||||
else if (timeZone == TimeZoneInfo.Local) {
|
||||
kind = DateTimeKind.Local;
|
||||
}
|
||||
|
||||
DateTime dateTime = new DateTime(1998, 1, 1, 10, 30, 30, kind);
|
||||
var dateTimeOffset = new DateTimeOffset(dateTime, timeZone.BaseUtcOffset);
|
||||
|
||||
// Print string using Gregorian calendar to avoid calendar conversion.
|
||||
var cultureGregorian = (CultureInfo)culture.Clone();
|
||||
cultureGregorian.DateTimeFormat.Calendar = cultureGregorian.OptionalCalendars.OfType<GregorianCalendar>().First();
|
||||
var dateTimeString = dateTimeOffset.ToString(dateTimeFormat, cultureGregorian);
|
||||
|
||||
// The .NET DateTimeOffset class is buggy. Firstly, it does not preserve the DateTimeKind value of the
|
||||
// DateTime from which it is created, causing it to never format "K" to "Z" for the UTC time zone. Secondly it
|
||||
// does not properly format "K" to an empty string for DateTimeKind.Unspecified. Our implementation
|
||||
// does not contain these bugs. Therefore for these two scenarios we use the DateTime formatting as a
|
||||
// reference instead.
|
||||
if (kind == DateTimeKind.Utc || (kind == DateTimeKind.Unspecified && dateTimeFormat.Contains('K'))) {
|
||||
dateTimeString = dateTime.ToString(dateTimeFormat, cultureGregorian);
|
||||
}
|
||||
|
||||
var caseKey = String.Format("{0}___{1}___{2}", culture.Name, dateTimeFormat, dateTimeString);
|
||||
allCases.Add(caseKey);
|
||||
//Debug.WriteLine(String.Format("{0} cases tested so far. Testing case {1}...", allCases.Count, caseKey));
|
||||
|
||||
try {
|
||||
var result = target.ParseDateTime(dateTimeString, dateTimeFormat);
|
||||
var expected = GetExpectedDateTimeParts(dateTime, dateTimeFormat, timeZone);
|
||||
Assert.AreEqual(expected, result);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
@@ -123,8 +191,8 @@ namespace Orchard.Framework.Tests.Localization {
|
||||
[Test]
|
||||
[Description("Date/time parsing throws a FormatException for unparsable date/time strings.")]
|
||||
[ExpectedException(typeof(FormatException))]
|
||||
public void ParseDateTimeTest03() {
|
||||
var container = InitializeContainer("en-US", null);
|
||||
public void ParseDateTimeTest04() {
|
||||
var container = InitializeContainer("en-US", null, TimeZoneInfo.Utc);
|
||||
var target = container.Resolve<IDateFormatter>();
|
||||
target.ParseDateTime("BlaBlaBla");
|
||||
}
|
||||
@@ -143,7 +211,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");
|
||||
var container = InitializeContainer(culture.Name, "GregorianCalendar", TimeZoneInfo.Utc);
|
||||
var formats = container.Resolve<IDateTimeFormatProvider>();
|
||||
var target = container.Resolve<IDateFormatter>();
|
||||
|
||||
@@ -182,7 +250,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);
|
||||
var container = InitializeContainer("en-US", null, TimeZoneInfo.Utc);
|
||||
var target = container.Resolve<IDateFormatter>();
|
||||
target.ParseDate("BlaBlaBla");
|
||||
}
|
||||
@@ -201,7 +269,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);
|
||||
var container = InitializeContainer(culture.Name, null, TimeZoneInfo.Utc);
|
||||
var formats = container.Resolve<IDateTimeFormatProvider>();
|
||||
var target = container.Resolve<IDateFormatter>();
|
||||
|
||||
@@ -217,7 +285,7 @@ namespace Orchard.Framework.Tests.Localization {
|
||||
|
||||
try {
|
||||
var result = target.ParseTime(timeString, timeFormat);
|
||||
var expected = GetExpectedTimeParts(time, timeFormat);
|
||||
var expected = GetExpectedTimeParts(time, timeFormat, TimeZoneInfo.Utc);
|
||||
Assert.AreEqual(expected, result);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
@@ -236,7 +304,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);
|
||||
var container = InitializeContainer("en-US", null, TimeZoneInfo.Utc);
|
||||
var target = container.Resolve<IDateFormatter>();
|
||||
target.ParseTime("BlaBlaBla");
|
||||
}
|
||||
@@ -255,7 +323,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");
|
||||
var container = InitializeContainer(culture.Name, "GregorianCalendar", TimeZoneInfo.Utc);
|
||||
var formats = container.Resolve<IDateTimeFormatProvider>();
|
||||
var target = container.Resolve<IDateFormatter>();
|
||||
|
||||
@@ -263,7 +331,7 @@ namespace Orchard.Framework.Tests.Localization {
|
||||
for (var month = 1; month <= 12; month++) { // All months in the year.
|
||||
|
||||
DateTime dateTime = new DateTime(1998, month, 1, 10, 30, 30);
|
||||
DateTimeParts dateTimeParts = new DateTimeParts(1998, month, 1, 10, 30, 30, 0);
|
||||
DateTimeParts dateTimeParts = new DateTimeParts(1998, month, 1, 10, 30, 30, 0, DateTimeKind.Unspecified, offset: TimeSpan.Zero);
|
||||
|
||||
// Print reference string using Gregorian calendar to avoid calendar conversion.
|
||||
var cultureGregorian = (CultureInfo)culture.Clone();
|
||||
@@ -318,7 +386,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");
|
||||
var container = InitializeContainer(culture.Name, "GregorianCalendar", TimeZoneInfo.Utc);
|
||||
var formats = container.Resolve<IDateTimeFormatProvider>();
|
||||
var target = container.Resolve<IDateFormatter>();
|
||||
|
||||
@@ -326,7 +394,7 @@ namespace Orchard.Framework.Tests.Localization {
|
||||
foreach (var hour in new[] { 0, 6, 12, 18 }) { // Enough hours to cover all code paths (AM/PM, 12<->00, 1/2 digits, etc).
|
||||
|
||||
DateTime dateTime = new DateTime(1998, 1, 1, hour, 30, 30);
|
||||
DateTimeParts dateTimeParts = new DateTimeParts(1998, 1, 1, hour, 30, 30, 0);
|
||||
DateTimeParts dateTimeParts = new DateTimeParts(1998, 1, 1, hour, 30, 30, 0, DateTimeKind.Unspecified, offset: TimeSpan.Zero);
|
||||
|
||||
// Print reference string using Gregorian calendar to avoid calendar conversion.
|
||||
var cultureGregorian = (CultureInfo)culture.Clone();
|
||||
@@ -367,6 +435,97 @@ namespace Orchard.Framework.Tests.Localization {
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Description("Date/time formatting works correctly for all combinations of kinds, time zones, format strings and cultures.")]
|
||||
public void FormatDateTimeTest03() {
|
||||
var allCases = new ConcurrentBag<string>();
|
||||
var failedCases = new ConcurrentDictionary<string, Exception>();
|
||||
var maxFailedCases = 0;
|
||||
|
||||
var options = new ParallelOptions();
|
||||
if (Debugger.IsAttached) {
|
||||
options.MaxDegreeOfParallelism = 1;
|
||||
}
|
||||
|
||||
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 formats = container.Resolve<IDateTimeFormatProvider>();
|
||||
var target = container.Resolve<IDateFormatter>();
|
||||
|
||||
foreach (var dateTimeFormat in formats.AllDateTimeFormats) { // All date/time formats supported by the culture.
|
||||
|
||||
// Unfortunately because the System.Globalization classes are tightly coupled to the
|
||||
// configured culture, calendar and time zone of the local machine, it's not possible
|
||||
// to test all scenarios and combinations while still relying on the .NET Framework
|
||||
// date/time formatting logic for reference. Therefore the logic of this test code takes
|
||||
// into account the configured local time zone of the machine when determining values for
|
||||
// DateTimeKind and offset. Less than ideal, but there really is no way around it.
|
||||
|
||||
var kind = DateTimeKind.Unspecified;
|
||||
var offset = timeZone.BaseUtcOffset;
|
||||
if (timeZone == TimeZoneInfo.Utc) {
|
||||
kind = DateTimeKind.Utc;
|
||||
}
|
||||
else if (timeZone == TimeZoneInfo.Local) {
|
||||
kind = DateTimeKind.Local;
|
||||
}
|
||||
|
||||
var dateTime = new DateTime(1998, 1, 1, 10, 30, 30, kind);
|
||||
var dateTimeOffset = new DateTimeOffset(dateTime, timeZone.BaseUtcOffset);
|
||||
var dateTimeParts = DateTimeParts.FromDateTime(dateTime, offset);
|
||||
|
||||
// Print reference string using Gregorian calendar to avoid calendar conversion.
|
||||
var cultureGregorian = (CultureInfo)culture.Clone();
|
||||
cultureGregorian.DateTimeFormat.Calendar = cultureGregorian.OptionalCalendars.OfType<GregorianCalendar>().First();
|
||||
|
||||
var caseKey = String.Format("{0}___{1}___{2}", culture.Name, dateTimeFormat, dateTimeParts);
|
||||
allCases.Add(caseKey);
|
||||
//Debug.WriteLine(String.Format("{0} cases tested so far. Testing case {1}...", allCases.Count, caseKey));
|
||||
|
||||
try {
|
||||
var result = target.FormatDateTime(dateTimeParts, dateTimeFormat);
|
||||
var expected = dateTimeOffset.ToString(dateTimeFormat, cultureGregorian);
|
||||
|
||||
// The .NET DateTimeOffset class is buggy. Firstly, it does not preserve the DateTimeKind value of the
|
||||
// DateTime from which it is created, causing it to never format "K" to "Z" for the UTC time zone. Secondly it
|
||||
// does not properly format "K" to an empty string for DateTimeKind.Unspecified. Our implementation
|
||||
// does not contain these bugs. Therefore for these two scenarios we use the DateTime formatting as a
|
||||
// reference instead.
|
||||
if (kind == DateTimeKind.Utc || (kind == DateTimeKind.Unspecified && dateTimeFormat.Contains('K'))) {
|
||||
expected = dateTime.ToString(dateTimeFormat, cultureGregorian);
|
||||
}
|
||||
|
||||
if (result != expected) {
|
||||
// The .NET date formatting logic contains a bug that causes it to recognize 'd' and 'dd'
|
||||
// as numerical day specifiers even when they are embedded in literals. Our implementation
|
||||
// does not contain this bug. If we encounter an unexpected result and the .NET reference
|
||||
// result contains the genitive month name, replace it with the non-genitive month name
|
||||
// before asserting.
|
||||
var numericalDayPattern = @"(\b|[^d])d{1,2}(\b|[^d])";
|
||||
var containsNumericalDay = Regex.IsMatch(dateTimeFormat, numericalDayPattern);
|
||||
if (containsNumericalDay) {
|
||||
var monthName = formats.MonthNames[0];
|
||||
var monthNameGenitive = formats.MonthNamesGenitive[0];
|
||||
expected = expected.Replace(monthNameGenitive, monthName);
|
||||
}
|
||||
}
|
||||
|
||||
Assert.AreEqual(expected, result);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
failedCases.TryAdd(caseKey, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (failedCases.Count > maxFailedCases) {
|
||||
throw new AggregateException(String.Format("Format tests failed for {0} of {1} cases. Expected {2} failed cases or less.", failedCases.Count, allCases.Count, maxFailedCases), failedCases.Values);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Description("Date formatting works correctly for all combinations of months, format strings and cultures.")]
|
||||
public void FormatDateTest01() {
|
||||
@@ -381,7 +540,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");
|
||||
var container = InitializeContainer(culture.Name, "GregorianCalendar", TimeZoneInfo.Utc);
|
||||
var formats = container.Resolve<IDateTimeFormatProvider>();
|
||||
var target = container.Resolve<IDateFormatter>();
|
||||
|
||||
@@ -444,7 +603,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);
|
||||
var container = InitializeContainer(culture.Name, null, TimeZoneInfo.Utc);
|
||||
var formats = container.Resolve<IDateTimeFormatProvider>();
|
||||
var target = container.Resolve<IDateFormatter>();
|
||||
|
||||
@@ -452,7 +611,7 @@ namespace Orchard.Framework.Tests.Localization {
|
||||
for (var hour = 0; hour <= 23; hour++) { // All hours in the day.
|
||||
|
||||
DateTime date = new DateTime(1998, 1, 1, hour, 30, 30);
|
||||
TimeParts timeParts = new TimeParts(hour, 30, 30, 0);
|
||||
TimeParts timeParts = new TimeParts(hour, 30, 30, 0, DateTimeKind.Unspecified, offset: TimeSpan.Zero);
|
||||
|
||||
var caseKey = String.Format("{0}___{1}___{2}", culture.Name, timeFormat, timeParts);
|
||||
allCases.Add(caseKey);
|
||||
@@ -475,10 +634,10 @@ namespace Orchard.Framework.Tests.Localization {
|
||||
}
|
||||
}
|
||||
|
||||
private DateTimeParts GetExpectedDateTimeParts(DateTime dateTime, string format) {
|
||||
private DateTimeParts GetExpectedDateTimeParts(DateTime dateTime, string format, TimeZoneInfo timeZone) {
|
||||
return new DateTimeParts(
|
||||
GetExpectedDateParts(dateTime, format),
|
||||
GetExpectedTimeParts(dateTime, format)
|
||||
GetExpectedTimeParts(dateTime, format, timeZone)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -491,19 +650,42 @@ namespace Orchard.Framework.Tests.Localization {
|
||||
);
|
||||
}
|
||||
|
||||
private TimeParts GetExpectedTimeParts(DateTime time, string format) {
|
||||
private TimeParts GetExpectedTimeParts(DateTime time, string format, TimeZoneInfo timeZone) {
|
||||
var formatWithoutLiterals = Regex.Replace(format, @"(?<!\\)'(.*?)(?<!\\)'|(?<!\\)""(.*?)(?<!\\)""", "");
|
||||
var expectedKind = DateTimeKind.Unspecified;
|
||||
if (formatWithoutLiterals.Contains('K') || formatWithoutLiterals.Contains('z')) {
|
||||
if (timeZone == TimeZoneInfo.Utc) {
|
||||
expectedKind = DateTimeKind.Utc;
|
||||
}
|
||||
else if (timeZone == TimeZoneInfo.Local) {
|
||||
expectedKind = DateTimeKind.Local;
|
||||
}
|
||||
}
|
||||
|
||||
var expectedOffset = TimeSpan.Zero;
|
||||
if (formatWithoutLiterals.Contains('K') && expectedKind != DateTimeKind.Unspecified) {
|
||||
expectedOffset = timeZone.BaseUtcOffset;
|
||||
}
|
||||
else if (formatWithoutLiterals.Contains("zzz")) {
|
||||
expectedOffset = timeZone.BaseUtcOffset;
|
||||
}
|
||||
else if (formatWithoutLiterals.Contains('z')) {
|
||||
expectedOffset = TimeSpan.FromHours(timeZone.BaseUtcOffset.Hours);
|
||||
}
|
||||
|
||||
return new TimeParts(
|
||||
formatWithoutLiterals.Contains('H') || format.Contains('h') ? time.Hour : 0,
|
||||
formatWithoutLiterals.Contains('m') ? time.Minute : 0,
|
||||
formatWithoutLiterals.Contains('s') ? time.Second : 0,
|
||||
formatWithoutLiterals.Contains('f') ? time.Millisecond : 0
|
||||
formatWithoutLiterals.Contains('f') ? time.Millisecond : 0,
|
||||
expectedKind,
|
||||
expectedOffset
|
||||
);
|
||||
}
|
||||
|
||||
private IContainer InitializeContainer(string cultureName, string calendarName) {
|
||||
private IContainer InitializeContainer(string cultureName, string calendarName, TimeZoneInfo timeZone) {
|
||||
var builder = new ContainerBuilder();
|
||||
builder.RegisterInstance<WorkContext>(new StubWorkContext(cultureName, calendarName));
|
||||
builder.RegisterInstance<WorkContext>(new StubWorkContext(cultureName, calendarName, timeZone));
|
||||
builder.RegisterType<StubWorkContextAccessor>().As<IWorkContextAccessor>();
|
||||
builder.RegisterType<CultureDateTimeFormatProvider>().As<IDateTimeFormatProvider>();
|
||||
builder.RegisterType<DefaultDateFormatter>().As<IDateFormatter>();
|
||||
@@ -516,10 +698,12 @@ namespace Orchard.Framework.Tests.Localization {
|
||||
|
||||
private string _cultureName;
|
||||
private string _calendarName;
|
||||
private TimeZoneInfo _timeZone;
|
||||
|
||||
public StubWorkContext(string cultureName, string calendarName) {
|
||||
public StubWorkContext(string cultureName, string calendarName, TimeZoneInfo timeZone) {
|
||||
_cultureName = cultureName;
|
||||
_calendarName = calendarName;
|
||||
_timeZone = timeZone;
|
||||
}
|
||||
|
||||
public override T Resolve<T>() {
|
||||
@@ -533,6 +717,7 @@ namespace Orchard.Framework.Tests.Localization {
|
||||
public override T GetState<T>(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));
|
||||
}
|
||||
|
||||
|
@@ -39,14 +39,15 @@ namespace Orchard.Localization.Models {
|
||||
}
|
||||
|
||||
public DateTime ToDateTime(Calendar calendar) {
|
||||
return calendar.ToDateTime(
|
||||
return new DateTime(
|
||||
_year > 0 ? _year : DateTime.MinValue.Year,
|
||||
_month > 0 ? _month : DateTime.MinValue.Month,
|
||||
_day > 0 ? _day : DateTime.MinValue.Day,
|
||||
DateTime.MinValue.Hour,
|
||||
DateTime.MinValue.Minute,
|
||||
DateTime.MinValue.Second,
|
||||
DateTime.MinValue.Millisecond
|
||||
DateTime.MinValue.Millisecond,
|
||||
calendar
|
||||
);
|
||||
}
|
||||
|
||||
|
@@ -6,20 +6,17 @@ using System.Linq;
|
||||
namespace Orchard.Localization.Models {
|
||||
public struct DateTimeParts {
|
||||
|
||||
//public static DateTimeParts? FromDateTime(DateTime? dateTime) {
|
||||
// if (!dateTime.HasValue) {
|
||||
// return null;
|
||||
// }
|
||||
// return FromDateTime(dateTime.Value);
|
||||
//}
|
||||
|
||||
public static DateTimeParts FromDateTime(DateTime dateTime) {
|
||||
return new DateTimeParts(dateTime.Year, dateTime.Month, dateTime.Day, dateTime.Hour, dateTime.Minute, dateTime.Second, dateTime.Millisecond);
|
||||
public static DateTimeParts FromDateTime(DateTime dateTime, TimeSpan offset) {
|
||||
return new DateTimeParts(dateTime.Year, dateTime.Month, dateTime.Day, dateTime.Hour, dateTime.Minute, dateTime.Second, dateTime.Millisecond, dateTime.Kind, offset);
|
||||
}
|
||||
|
||||
public DateTimeParts(int year, int month, int day, int hour, int minute, int second, int millisecond) {
|
||||
public static DateTimeParts FromDateTimeOffset(DateTimeOffset dateTimeOffset) {
|
||||
return new DateTimeParts(dateTimeOffset.Year, dateTimeOffset.Month, dateTimeOffset.Day, dateTimeOffset.Hour, dateTimeOffset.Minute, dateTimeOffset.Second, dateTimeOffset.Millisecond, dateTimeOffset.DateTime.Kind, dateTimeOffset.Offset);
|
||||
}
|
||||
|
||||
public DateTimeParts(int year, int month, int day, int hour, int minute, int second, int millisecond, DateTimeKind kind, TimeSpan offset) {
|
||||
_date = new DateParts(year, month, day);
|
||||
_time = new TimeParts(hour, minute, second, millisecond);
|
||||
_time = new TimeParts(hour, minute, second, millisecond, kind, offset);
|
||||
}
|
||||
|
||||
public DateTimeParts(DateParts dateParts, TimeParts timeParts) {
|
||||
@@ -43,14 +40,16 @@ namespace Orchard.Localization.Models {
|
||||
}
|
||||
|
||||
public DateTime ToDateTime(Calendar calendar) {
|
||||
return calendar.ToDateTime(
|
||||
return new DateTime(
|
||||
Date.Year > 0 ? Date.Year : DateTime.MinValue.Year,
|
||||
Date.Month > 0 ? Date.Month : DateTime.MinValue.Month,
|
||||
Date.Day > 0 ? Date.Day : DateTime.MinValue.Day,
|
||||
Time.Hour > 0 ? Time.Hour : DateTime.MinValue.Hour,
|
||||
Time.Minute > 0 ? Time.Minute : DateTime.MinValue.Minute,
|
||||
Time.Second > 0 ? Time.Second : DateTime.MinValue.Second,
|
||||
Time.Millisecond > 0 ? Time.Millisecond : DateTime.MinValue.Millisecond
|
||||
Time.Millisecond > 0 ? Time.Millisecond : DateTime.MinValue.Millisecond,
|
||||
calendar,
|
||||
Time.Kind
|
||||
);
|
||||
}
|
||||
|
||||
|
@@ -7,21 +7,28 @@ namespace Orchard.Localization.Models {
|
||||
|
||||
public static TimeParts MinValue {
|
||||
get {
|
||||
return new TimeParts(DateTime.MinValue.Hour, DateTime.MinValue.Minute, DateTime.MinValue.Second, DateTime.MinValue.Millisecond);
|
||||
return new TimeParts(DateTime.MinValue.Hour, DateTime.MinValue.Minute, DateTime.MinValue.Second, DateTime.MinValue.Millisecond, DateTimeKind.Unspecified, offset: TimeSpan.Zero);
|
||||
}
|
||||
}
|
||||
|
||||
public TimeParts(int hour, int minute, int second, int millisecond) {
|
||||
public TimeParts(int hour, int minute, int second, int millisecond, DateTimeKind kind, TimeSpan offset) {
|
||||
if (kind == DateTimeKind.Utc && offset != TimeSpan.Zero) {
|
||||
throw new ArgumentOutOfRangeException(String.Format("The specified offset {0} does not match the specified kind {1}.", offset, kind));
|
||||
}
|
||||
_hour = hour;
|
||||
_minute = minute;
|
||||
_second = second;
|
||||
_millisecond = millisecond;
|
||||
_kind = kind;
|
||||
_offset = offset;
|
||||
}
|
||||
|
||||
private readonly int _hour;
|
||||
private readonly int _minute;
|
||||
private readonly int _second;
|
||||
private readonly int _millisecond;
|
||||
private readonly DateTimeKind _kind;
|
||||
private readonly TimeSpan _offset;
|
||||
|
||||
public int Hour {
|
||||
get {
|
||||
@@ -47,6 +54,18 @@ namespace Orchard.Localization.Models {
|
||||
}
|
||||
}
|
||||
|
||||
public DateTimeKind Kind {
|
||||
get {
|
||||
return _kind;
|
||||
}
|
||||
}
|
||||
|
||||
public TimeSpan? Offset {
|
||||
get {
|
||||
return _offset;
|
||||
}
|
||||
}
|
||||
|
||||
public DateTime ToDateTime() {
|
||||
return new DateTime(
|
||||
DateTime.MinValue.Year,
|
||||
@@ -55,12 +74,13 @@ namespace Orchard.Localization.Models {
|
||||
_hour > 0 ? _hour : DateTime.MinValue.Hour,
|
||||
_minute > 0 ? _minute : DateTime.MinValue.Minute,
|
||||
_second > 0 ? _second : DateTime.MinValue.Second,
|
||||
_millisecond > 0 ? _millisecond : DateTime.MinValue.Millisecond
|
||||
_millisecond > 0 ? _millisecond : DateTime.MinValue.Millisecond,
|
||||
_kind
|
||||
);
|
||||
}
|
||||
|
||||
public override string ToString() {
|
||||
return String.Format("{0}:{1}:{2}.{3}", _hour, _minute, _second, _millisecond);
|
||||
return String.Format("{0}:{1}:{2}.{3}-{4}-{5}", _hour, _minute, _second, _millisecond, _kind, _offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -108,13 +108,13 @@ namespace Orchard.Localization.Services {
|
||||
var formatString = ConvertToFormatString(format, replacements);
|
||||
var calendar = CurrentCalendar;
|
||||
|
||||
int twoDigitYear, hour12;
|
||||
int twoDigitYear, hour12, offsetHours, offsetMinutes;
|
||||
bool isPm;
|
||||
string monthName, monthNameShort, monthNameGenitive, monthNameShortGenitive, dayName, dayNameShort, amPm, amPmShort, timeZone;
|
||||
string monthName, monthNameShort, monthNameGenitive, monthNameShortGenitive, dayName, dayNameShort, amPm, amPmShort, timeZone, offsetSign;
|
||||
GetDateFormatValues(parts.Date, calendar, out twoDigitYear, out monthName, out monthNameShort, out monthNameGenitive, out monthNameShortGenitive, out dayName, out dayNameShort);
|
||||
GetTimeFormatValues(parts.Time, out isPm, out hour12, out amPm, out amPmShort, out timeZone);
|
||||
GetTimeFormatValues(parts.Time, out isPm, out hour12, out amPm, out amPmShort, out timeZone, out offsetSign, out offsetHours, out offsetMinutes);
|
||||
|
||||
return String.Format(formatString, parts.Date.Year, twoDigitYear, parts.Date.Month, monthName, monthNameShort, monthNameGenitive, monthNameShortGenitive, parts.Date.Day, dayName, dayNameShort, parts.Time.Hour, hour12, parts.Time.Minute, parts.Time.Second, parts.Time.Millisecond, amPm, amPmShort, timeZone);
|
||||
return String.Format(formatString, parts.Date.Year, twoDigitYear, parts.Date.Month, monthName, monthNameShort, monthNameGenitive, monthNameShortGenitive, parts.Date.Day, dayName, dayNameShort, parts.Time.Hour, hour12, parts.Time.Minute, parts.Time.Second, parts.Time.Millisecond, amPm, amPmShort, timeZone, offsetSign, offsetHours, offsetMinutes);
|
||||
}
|
||||
|
||||
public virtual string FormatDate(DateParts parts) {
|
||||
@@ -144,11 +144,11 @@ namespace Orchard.Localization.Services {
|
||||
var formatString = ConvertToFormatString(format, replacements);
|
||||
|
||||
bool isPm;
|
||||
int hour12;
|
||||
string amPm, amPmShort, timeZone;
|
||||
GetTimeFormatValues(parts, out isPm, out hour12, out amPm, out amPmShort, out timeZone);
|
||||
int hour12, offsetHours, offsetMinutes;
|
||||
string amPm, amPmShort, timeZone, offsetSign;
|
||||
GetTimeFormatValues(parts, out isPm, out hour12, out amPm, out amPmShort, out timeZone, out offsetSign, out offsetHours, out offsetMinutes);
|
||||
|
||||
return String.Format(formatString, null, null, null, null, null, null, null, null, null, null, parts.Hour, hour12, parts.Minute, parts.Second, parts.Millisecond, amPm, amPmShort, timeZone);
|
||||
return String.Format(formatString, null, null, null, null, null, null, null, null, null, null, parts.Hour, hour12, parts.Minute, parts.Second, parts.Millisecond, amPm, amPmShort, timeZone, offsetSign, offsetHours, offsetMinutes);
|
||||
}
|
||||
|
||||
protected virtual DateTimeParts? TryParseDateTime(string dateTimeString, string format, IDictionary<string, string> replacements) {
|
||||
@@ -227,6 +227,9 @@ namespace Orchard.Localization.Services {
|
||||
second = 0,
|
||||
millisecond = 0;
|
||||
|
||||
var kind = DateTimeKind.Unspecified;
|
||||
var offset = TimeSpan.Zero;
|
||||
|
||||
// For the hour we can either use 24-hour notation or 12-hour notation in combination with AM/PM designator.
|
||||
if (m.Groups["hour24"].Success) {
|
||||
hour = Int32.Parse(m.Groups["hour24"].Value);
|
||||
@@ -251,7 +254,40 @@ namespace Orchard.Localization.Services {
|
||||
millisecond = Int32.Parse(m.Groups["millisecond"].Value);
|
||||
}
|
||||
|
||||
return new TimeParts(hour, minute, second, millisecond);
|
||||
if (m.Groups["timeZone"].Success) {
|
||||
var timeZoneString = m.Groups["timeZone"].Value;
|
||||
if (timeZoneString.ToUpperInvariant() == "Z") {
|
||||
kind = DateTimeKind.Utc;
|
||||
}
|
||||
else {
|
||||
offset = TimeSpan.Parse(timeZoneString.TrimStart('+'));
|
||||
if (offset == CurrentTimeZone.BaseUtcOffset) {
|
||||
kind = DateTimeKind.Local;
|
||||
}
|
||||
else if (offset == TimeSpan.Zero) {
|
||||
kind = DateTimeKind.Utc;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (m.Groups["offsetSign"].Success && m.Groups["offsetHours"].Success) {
|
||||
var offsetHours = Int32.Parse(m.Groups["offsetHours"].Value);
|
||||
var offsetMinutes = 0;
|
||||
if (m.Groups["offsetMinutes"].Success) {
|
||||
offsetMinutes = Int32.Parse(m.Groups["offsetMinutes"].Value);
|
||||
}
|
||||
offset = new TimeSpan(offsetHours, offsetMinutes, 0);
|
||||
if (m.Groups["offsetSign"].Value == "-") {
|
||||
offset = offset.Negate();
|
||||
}
|
||||
if (offset == CurrentTimeZone.BaseUtcOffset) {
|
||||
kind = DateTimeKind.Local;
|
||||
}
|
||||
else if (offset == TimeSpan.Zero) {
|
||||
kind = DateTimeKind.Utc;
|
||||
}
|
||||
}
|
||||
|
||||
return new TimeParts(hour, minute, second, millisecond, kind, offset);
|
||||
}
|
||||
|
||||
protected virtual void GetDateFormatValues(DateParts parts, Calendar calendar, out int twoDigitYear, out string monthName, out string monthNameShort, out string monthNameGenitive, out string monthNameShortGenitive, out string dayName, out string dayNameShort) {
|
||||
@@ -265,11 +301,22 @@ namespace Orchard.Localization.Services {
|
||||
dayNameShort = parts.Day > 0 ? _dateTimeFormatProvider.DayNamesShort[(int)calendar.GetDayOfWeek(parts.ToDateTime(calendar))] : null;
|
||||
}
|
||||
|
||||
protected virtual void GetTimeFormatValues(TimeParts parts, out bool isPm, out int hour12, out string amPm, out string amPmShort, out string timeZone) {
|
||||
protected virtual void GetTimeFormatValues(TimeParts parts, out bool isPm, out int hour12, out string amPm, out string amPmShort, out string timeZone, out string offsetSign, out int offsetHours, out int offsetMinutes) {
|
||||
hour12 = ConvertToHour12(parts.Hour, out isPm);
|
||||
amPm = _dateTimeFormatProvider.AmPmDesignators[isPm ? 1 : 0];
|
||||
amPmShort = String.IsNullOrEmpty(amPm) ? "" : amPm[0].ToString();
|
||||
timeZone = ""; // TODO: Add time zone information to TimeParts and print this out correctly.
|
||||
offsetSign = parts.Offset.GetValueOrDefault() < TimeSpan.Zero ? "-" : "+";
|
||||
offsetHours = Math.Abs(parts.Offset.GetValueOrDefault().Hours);
|
||||
offsetMinutes = Math.Abs(parts.Offset.GetValueOrDefault().Minutes);
|
||||
timeZone = "";
|
||||
switch (parts.Kind) {
|
||||
case DateTimeKind.Utc:
|
||||
timeZone = "Z";
|
||||
break;
|
||||
case DateTimeKind.Local:
|
||||
timeZone = String.Format("{0}{1:00}:{2:00}", offsetSign, offsetHours, offsetMinutes);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual bool GetUseGenitiveMonthName(string format) {
|
||||
@@ -318,7 +365,10 @@ namespace Orchard.Localization.Services {
|
||||
{"f", "(?<millisecond>[0-9]{1})"},
|
||||
{"tt", String.Format(@"\s*(?<amPm>{0}|{1})\s*", EscapeForRegex(amDesignator), EscapeForRegex(pmDesignator))},
|
||||
{"t", String.Format(@"\s*(?<amPm>{0}|{1})\s*", String.IsNullOrEmpty(amDesignator) ? "" : EscapeForRegex(amDesignator[0].ToString()), String.IsNullOrEmpty(pmDesignator) ? "" : EscapeForRegex(pmDesignator[0].ToString()))},
|
||||
{"K", @"(?<timezone>Z|(\+|-)[0-9]{2}:[0-9]{2})*"}
|
||||
{"K", @"(?<timeZone>Z|(\+|-)[0-9]{2}:[0-9]{2})*"},
|
||||
{"zzz", @"(?<offsetSign>\+|-)(?<offsetHours>[0-9]{2}):(?<offsetMinutes>[0-9]{2})"},
|
||||
{"zz", @"(?<offsetSign>\+|-)(?<offsetHours>[0-9]{2})"},
|
||||
{"z", @"(?<offsetSign>\+|-)(?<offsetHours>[0-9]{1,2})"}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -368,7 +418,10 @@ namespace Orchard.Localization.Services {
|
||||
{"F", "{14:#}"},
|
||||
{"tt", "{15}"},
|
||||
{"t", "{16}"},
|
||||
{"K", "{17}"}
|
||||
{"K", "{17}"},
|
||||
{"zzz", "{18}{19:00}:{20:00}"},
|
||||
{"zz", "{18}{19:00}"},
|
||||
{"z", "{18}{19:#0}"}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -457,5 +510,12 @@ namespace Orchard.Localization.Services {
|
||||
return CurrentCulture.Calendar;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual TimeZoneInfo CurrentTimeZone {
|
||||
get {
|
||||
var workContext = _workContextAccessor.GetContext();
|
||||
return workContext.CurrentTimeZone;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -38,7 +38,7 @@ namespace Orchard.Localization.Services {
|
||||
return TimeZoneInfo.ConvertTimeToUtc(date, workContext.CurrentTimeZone);
|
||||
}
|
||||
|
||||
public virtual DateTimeParts ConvertToSiteCalendar(DateTime date) {
|
||||
public virtual DateTimeParts ConvertToSiteCalendar(DateTime date, TimeSpan offset) {
|
||||
var calendar = CurrentCalendar;
|
||||
return new DateTimeParts(
|
||||
calendar.GetYear(date),
|
||||
@@ -47,7 +47,9 @@ namespace Orchard.Localization.Services {
|
||||
calendar.GetHour(date),
|
||||
calendar.GetMinute(date),
|
||||
calendar.GetSecond(date),
|
||||
Convert.ToInt32(calendar.GetMilliseconds(date)));
|
||||
Convert.ToInt32(calendar.GetMilliseconds(date)),
|
||||
DateTimeKind.Utc,
|
||||
offset);
|
||||
}
|
||||
|
||||
public virtual DateTime ConvertFromSiteCalendar(DateTimeParts parts) {
|
||||
@@ -74,6 +76,7 @@ namespace Orchard.Localization.Services {
|
||||
}
|
||||
|
||||
var dateValue = date.Value;
|
||||
var offset = TimeSpan.Zero;
|
||||
|
||||
if (options.EnableTimeZoneConversion) {
|
||||
// Since no date component is expected (technically the date component is that of DateTime.MinValue) then
|
||||
@@ -82,16 +85,18 @@ 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;
|
||||
}
|
||||
|
||||
var parts = DateTimeParts.FromDateTime(dateValue);
|
||||
// No calendar conversion in this method - we expect the date component to be DateTime.MinValue and irrelevant anyway.
|
||||
//if (options.EnableCalendarConversion && !(CurrentCalendar is GregorianCalendar)) {
|
||||
// parts = ConvertToSiteCalendar(dateValue);
|
||||
//}
|
||||
var parts = DateTimeParts.FromDateTime(dateValue, offset);
|
||||
|
||||
// INFO: No calendar conversion in this method - we expect the date component to be DateTime.MinValue and irrelevant anyway.
|
||||
|
||||
return _dateFormatter.FormatDateTime(parts, _dateTimeFormatProvider.LongTimeFormat);
|
||||
}
|
||||
@@ -116,14 +121,17 @@ namespace Orchard.Localization.Services {
|
||||
}
|
||||
|
||||
var dateValue = date.Value;
|
||||
var offset = TimeSpan.Zero;
|
||||
|
||||
if (options.EnableTimeZoneConversion) {
|
||||
dateValue = ConvertToSiteTimeZone(dateValue);
|
||||
var workContext = _workContextAccessor.GetContext();
|
||||
offset = workContext.CurrentTimeZone.BaseUtcOffset;
|
||||
}
|
||||
|
||||
var parts = DateTimeParts.FromDateTime(dateValue);
|
||||
var parts = DateTimeParts.FromDateTime(dateValue, offset);
|
||||
if (options.EnableCalendarConversion && !(CurrentCalendar is GregorianCalendar)) {
|
||||
parts = ConvertToSiteCalendar(dateValue);
|
||||
parts = ConvertToSiteCalendar(dateValue, offset);
|
||||
}
|
||||
|
||||
return _dateFormatter.FormatDateTime(parts, format);
|
||||
|
@@ -28,8 +28,9 @@ namespace Orchard.Localization.Services {
|
||||
/// Converts a date from Gregorian calendar to the Orchard configured calendar.
|
||||
/// </summary>
|
||||
/// <param name="date">The Gregorian calendar date to convert.</param>
|
||||
/// <param name="offset">A TimeSpan representing the offset from UTC of the supplied date parameter.</param>
|
||||
/// <returns>A <c>DateTimeParts</c> instance representing the converted date.</returns>
|
||||
DateTimeParts ConvertToSiteCalendar(DateTime date);
|
||||
DateTimeParts ConvertToSiteCalendar(DateTime date, TimeSpan offset);
|
||||
|
||||
/// <summary>
|
||||
/// Converts a date from the Orchard configured calendar to Gregorian calendar.
|
||||
|
@@ -1,6 +1,5 @@
|
||||
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 support for the different Gregorian calendar types
|
||||
* Improve CultureDateTimeFormatProvider to return correct information for fa-IR culture when PersianCalendar is in use.
|
||||
|
Reference in New Issue
Block a user