예제 #1
0
        public string ConvertToLocalizedString(DateTime?date, string format, DateLocalizationOptions options = null)
        {
            options = options ?? new DateLocalizationOptions();

            if (!date.HasValue)
            {
                return(options.NullText);
            }

            var dateValue = date.Value;
            var offset    = TimeSpan.Zero;

            if (options.EnableTimeZoneConversion)
            {
                dateValue = ConvertToSiteTimeZone(dateValue);
                offset    = CurrentTimeZone.GetUtcOffset(date.Value);
            }

            var parts = DateTimeParts.FromDateTime(dateValue, offset);

            if (options.EnableCalendarConversion && !(CurrentCalendar is GregorianCalendar))
            {
                parts = ConvertToSiteCalendar(dateValue, offset);
            }

            return(_dateFormatter.FormatDateTime(parts, format));
        }
예제 #2
0
        public string ConvertToLocalizedTimeString(DateTime?date, DateLocalizationOptions options = null)
        {
            options = options ?? new DateLocalizationOptions();

            if (!date.HasValue)
            {
                return(options.NullText);
            }

            var dateValue = date.Value;
            var offset    = TimeSpan.Zero;

            if (options.EnableTimeZoneConversion)
            {
                if (options.IgnoreDate)
                {
                    // The caller has asked us to ignore the date part. This usually because the source
                    // is a time-only field. In such cases (with an undefined date) it does not make sense
                    // to consider DST variations throughout the year, so we will use an arbitrary (but fixed)
                    // non-DST date for the conversion to ensure DST is never applied during conversion. The
                    // date part is usually DateTime.MinValue which we should not use because time zone
                    // conversion cannot wrap DateTime.MinValue around to the previous day, resulting in
                    // an undefined result. Instead we convert the date to a hard-coded date of 2000-01-01
                    // before the conversion, and back to the original date after.
                    var tempDate = new DateTime(2000, 1, 1, dateValue.Hour, dateValue.Minute, dateValue.Second, dateValue.Millisecond, dateValue.Kind);
                    tempDate  = ConvertToSiteTimeZone(tempDate);
                    dateValue = new DateTime(dateValue.Year, dateValue.Month, dateValue.Day, tempDate.Hour, tempDate.Minute, tempDate.Second, tempDate.Millisecond, tempDate.Kind);
                }
                else
                {
                    dateValue = ConvertToSiteTimeZone(dateValue);
                }

                offset = CurrentTimeZone.GetUtcOffset(date.Value);
            }

            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));
        }
예제 #3
0
        public string ConvertToLocalizedTimeString(DateTime?date, DateLocalizationOptions options = null)
        {
            options = options ?? new DateLocalizationOptions();

            if (!date.HasValue)
            {
                return(options.NullText);
            }

            var dateValue = date.Value;
            var offset    = TimeSpan.Zero;

            if (options.EnableTimeZoneConversion)
            {
                if (options.IgnoreDate)
                {
                    // The caller has asked us to ignore the date part and assume it is today. This usually because the source
                    // is a time-only field, in which case the date part is usually DateTime.MinValue which we should not use
                    // for the following 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 the original date after.
                    var today    = _clock.UtcNow.Date;
                    var tempDate = new DateTime(today.Year, today.Month, today.Day, dateValue.Hour, dateValue.Minute, dateValue.Second, dateValue.Millisecond, dateValue.Kind);
                    tempDate  = ConvertToSiteTimeZone(tempDate);
                    dateValue = new DateTime(dateValue.Year, dateValue.Month, dateValue.Day, tempDate.Hour, tempDate.Minute, tempDate.Second, tempDate.Millisecond, tempDate.Kind);
                }
                else
                {
                    dateValue = ConvertToSiteTimeZone(dateValue);
                }

                offset = CurrentTimeZone.GetUtcOffset(date.Value);
            }

            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));
        }
        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 = TestHelpers.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, 678, 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);
            }
        }