Optimize DateTime decomposition in CoreLib (#1944)
authorts2do <tsdodo@gmail.com>
Tue, 21 Jan 2020 20:01:36 +0000 (14:01 -0600)
committerDan Moseley <danmose@microsoft.com>
Tue, 21 Jan 2020 20:01:36 +0000 (12:01 -0800)
* Optimize DateTime decomposition in CoreLib
Add uses of DateTime.GetDate (renamed from GetDatePart) where appropriate
Add DateTime.GetTime method to eliminate duplicate computations

* Remove unused arithmetic from DateTime.GetTime

14 files changed:
src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Date.G.cs
src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Date.L.cs
src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Date.O.cs
src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Date.R.cs
src/libraries/System.Private.CoreLib/src/System/DateTime.Windows.cs
src/libraries/System.Private.CoreLib/src/System/DateTime.cs
src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeFormat.cs
src/libraries/System.Private.CoreLib/src/System/Globalization/EastAsianLunisolarCalendar.cs
src/libraries/System.Private.CoreLib/src/System/Globalization/GregorianCalendar.cs
src/libraries/System.Private.CoreLib/src/System/Globalization/HebrewCalendar.cs
src/libraries/System.Private.CoreLib/src/System/Globalization/UmAlQuraCalendar.cs
src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.AdjustmentRule.cs
src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.TransitionTime.cs
src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.cs

index 7c4a2e342d89521b538182827551948c17202504..884fdebf8d2fc7335b2b936226bce70fc159edfa 100644 (file)
@@ -41,47 +41,49 @@ namespace System.Buffers.Text
             // Hoist most of the bounds checks on buffer.
             { var unused = destination[MinimumBytesNeeded - 1]; }
 
-            // TODO: Introduce an API which can parse DateTime instances efficiently, pulling out
-            // all their properties (Month, Day, etc.) in one shot. This would help avoid the
-            // duplicate work that implicitly results from calling these properties individually.
+            value.GetDate(out int year, out int month, out int day);
+            value.GetTime(out int hour, out int minute, out int second);
 
-            FormattingHelpers.WriteTwoDecimalDigits((uint)value.Month, destination, 0);
+            FormattingHelpers.WriteTwoDecimalDigits((uint)month, destination, 0);
             destination[2] = Utf8Constants.Slash;
 
-            FormattingHelpers.WriteTwoDecimalDigits((uint)value.Day, destination, 3);
+            FormattingHelpers.WriteTwoDecimalDigits((uint)day, destination, 3);
             destination[5] = Utf8Constants.Slash;
 
-            FormattingHelpers.WriteFourDecimalDigits((uint)value.Year, destination, 6);
+            FormattingHelpers.WriteFourDecimalDigits((uint)year, destination, 6);
             destination[10] = Utf8Constants.Space;
 
-            FormattingHelpers.WriteTwoDecimalDigits((uint)value.Hour, destination, 11);
+            FormattingHelpers.WriteTwoDecimalDigits((uint)hour, destination, 11);
             destination[13] = Utf8Constants.Colon;
 
-            FormattingHelpers.WriteTwoDecimalDigits((uint)value.Minute, destination, 14);
+            FormattingHelpers.WriteTwoDecimalDigits((uint)minute, destination, 14);
             destination[16] = Utf8Constants.Colon;
 
-            FormattingHelpers.WriteTwoDecimalDigits((uint)value.Second, destination, 17);
+            FormattingHelpers.WriteTwoDecimalDigits((uint)second, destination, 17);
 
             if (offset != Utf8Constants.NullUtcOffset)
             {
+                int offsetTotalMinutes = (int)(offset.Ticks / TimeSpan.TicksPerMinute);
                 byte sign;
 
-                if (offset < default(TimeSpan) /* a "const" version of TimeSpan.Zero */)
+                if (offsetTotalMinutes < 0)
                 {
                     sign = Utf8Constants.Minus;
-                    offset = TimeSpan.FromTicks(-offset.Ticks);
+                    offsetTotalMinutes = -offsetTotalMinutes;
                 }
                 else
                 {
                     sign = Utf8Constants.Plus;
                 }
 
+                int offsetHours = Math.DivRem(offsetTotalMinutes, 60, out int offsetMinutes);
+
                 // Writing the value backward allows the JIT to optimize by
                 // performing a single bounds check against buffer.
 
-                FormattingHelpers.WriteTwoDecimalDigits((uint)offset.Minutes, destination, 24);
+                FormattingHelpers.WriteTwoDecimalDigits((uint)offsetMinutes, destination, 24);
                 destination[23] = Utf8Constants.Colon;
-                FormattingHelpers.WriteTwoDecimalDigits((uint)offset.Hours, destination, 21);
+                FormattingHelpers.WriteTwoDecimalDigits((uint)offsetHours, destination, 21);
                 destination[20] = sign;
                 destination[19] = Utf8Constants.Space;
             }
index 699f91f39c8d94d5bd06b11e5f3591b1a45848ee..129c0a49bd5cb1cbf8146fdbb2790c3ca0ae27d1 100644 (file)
@@ -22,6 +22,9 @@ namespace System.Buffers.Text
                 return false;
             }
 
+            value.GetDate(out int year, out int month, out int day);
+            value.GetTime(out int hour, out int minute, out int second);
+
             uint dayAbbrev = s_dayAbbreviationsLowercase[(int)value.DayOfWeek];
 
             destination[0] = (byte)dayAbbrev;
@@ -32,10 +35,10 @@ namespace System.Buffers.Text
             destination[3] = Utf8Constants.Comma;
             destination[4] = Utf8Constants.Space;
 
-            FormattingHelpers.WriteTwoDecimalDigits((uint)value.Day, destination, 5);
+            FormattingHelpers.WriteTwoDecimalDigits((uint)day, destination, 5);
             destination[7] = Utf8Constants.Space;
 
-            uint monthAbbrev = s_monthAbbreviationsLowercase[value.Month - 1];
+            uint monthAbbrev = s_monthAbbreviationsLowercase[month - 1];
             destination[8] = (byte)monthAbbrev;
             monthAbbrev >>= 8;
             destination[9] = (byte)monthAbbrev;
@@ -43,16 +46,16 @@ namespace System.Buffers.Text
             destination[10] = (byte)monthAbbrev;
             destination[11] = Utf8Constants.Space;
 
-            FormattingHelpers.WriteFourDecimalDigits((uint)value.Year, destination, 12);
+            FormattingHelpers.WriteFourDecimalDigits((uint)year, destination, 12);
             destination[16] = Utf8Constants.Space;
 
-            FormattingHelpers.WriteTwoDecimalDigits((uint)value.Hour, destination, 17);
+            FormattingHelpers.WriteTwoDecimalDigits((uint)hour, destination, 17);
             destination[19] = Utf8Constants.Colon;
 
-            FormattingHelpers.WriteTwoDecimalDigits((uint)value.Minute, destination, 20);
+            FormattingHelpers.WriteTwoDecimalDigits((uint)minute, destination, 20);
             destination[22] = Utf8Constants.Colon;
 
-            FormattingHelpers.WriteTwoDecimalDigits((uint)value.Second, destination, 23);
+            FormattingHelpers.WriteTwoDecimalDigits((uint)second, destination, 23);
             destination[25] = Utf8Constants.Space;
 
             destination[26] = GMT1Lowercase;
index 2bbfcaaee8a57571ed7eaecd285c5b18307d3eac..29b9c2cb3cbb09105c69b5c6941d063814ff37df 100644 (file)
@@ -51,46 +51,52 @@ namespace System.Buffers.Text
             // Hoist most of the bounds checks on buffer.
             { _ = destination[MinimumBytesNeeded - 1]; }
 
-            FormattingHelpers.WriteFourDecimalDigits((uint)value.Year, destination, 0);
+            value.GetDate(out int year, out int month, out int day);
+            value.GetTimePrecise(out int hour, out int minute, out int second, out int ticks);
+
+            FormattingHelpers.WriteFourDecimalDigits((uint)year, destination, 0);
             destination[4] = Utf8Constants.Minus;
 
-            FormattingHelpers.WriteTwoDecimalDigits((uint)value.Month, destination, 5);
+            FormattingHelpers.WriteTwoDecimalDigits((uint)month, destination, 5);
             destination[7] = Utf8Constants.Minus;
 
-            FormattingHelpers.WriteTwoDecimalDigits((uint)value.Day, destination, 8);
+            FormattingHelpers.WriteTwoDecimalDigits((uint)day, destination, 8);
             destination[10] = TimeMarker;
 
-            FormattingHelpers.WriteTwoDecimalDigits((uint)value.Hour, destination, 11);
+            FormattingHelpers.WriteTwoDecimalDigits((uint)hour, destination, 11);
             destination[13] = Utf8Constants.Colon;
 
-            FormattingHelpers.WriteTwoDecimalDigits((uint)value.Minute, destination, 14);
+            FormattingHelpers.WriteTwoDecimalDigits((uint)minute, destination, 14);
             destination[16] = Utf8Constants.Colon;
 
-            FormattingHelpers.WriteTwoDecimalDigits((uint)value.Second, destination, 17);
+            FormattingHelpers.WriteTwoDecimalDigits((uint)second, destination, 17);
             destination[19] = Utf8Constants.Period;
 
-            FormattingHelpers.WriteDigits((uint)((ulong)value.Ticks % (ulong)TimeSpan.TicksPerSecond), destination.Slice(20, 7));
+            FormattingHelpers.WriteDigits((uint)ticks, destination.Slice(20, 7));
 
             if (kind == DateTimeKind.Local)
             {
+                int offsetTotalMinutes = (int)(offset.Ticks / TimeSpan.TicksPerMinute);
                 byte sign;
 
-                if (offset < default(TimeSpan) /* a "const" version of TimeSpan.Zero */)
+                if (offsetTotalMinutes < 0)
                 {
                     sign = Utf8Constants.Minus;
-                    offset = TimeSpan.FromTicks(-offset.Ticks);
+                    offsetTotalMinutes = -offsetTotalMinutes;
                 }
                 else
                 {
                     sign = Utf8Constants.Plus;
                 }
 
+                int offsetHours = Math.DivRem(offsetTotalMinutes, 60, out int offsetMinutes);
+
                 // Writing the value backward allows the JIT to optimize by
                 // performing a single bounds check against buffer.
 
-                FormattingHelpers.WriteTwoDecimalDigits((uint)offset.Minutes, destination, 31);
+                FormattingHelpers.WriteTwoDecimalDigits((uint)offsetMinutes, destination, 31);
                 destination[30] = Utf8Constants.Colon;
-                FormattingHelpers.WriteTwoDecimalDigits((uint)offset.Hours, destination, 28);
+                FormattingHelpers.WriteTwoDecimalDigits((uint)offsetHours, destination, 28);
                 destination[27] = sign;
             }
             else if (kind == DateTimeKind.Utc)
index dd9ec459b7b8661fd7e7573c8951f347fca6ac38..bd14d8201f6f6935c7b42f4fb97f0272480ba94a 100644 (file)
@@ -22,6 +22,9 @@ namespace System.Buffers.Text
                 return false;
             }
 
+            value.GetDate(out int year, out int month, out int day);
+            value.GetTime(out int hour, out int minute, out int second);
+
             uint dayAbbrev = s_dayAbbreviations[(int)value.DayOfWeek];
 
             destination[0] = (byte)dayAbbrev;
@@ -32,10 +35,10 @@ namespace System.Buffers.Text
             destination[3] = Utf8Constants.Comma;
             destination[4] = Utf8Constants.Space;
 
-            FormattingHelpers.WriteTwoDecimalDigits((uint)value.Day, destination, 5);
+            FormattingHelpers.WriteTwoDecimalDigits((uint)day, destination, 5);
             destination[7] = Utf8Constants.Space;
 
-            uint monthAbbrev = s_monthAbbreviations[value.Month - 1];
+            uint monthAbbrev = s_monthAbbreviations[month - 1];
             destination[8] = (byte)monthAbbrev;
             monthAbbrev >>= 8;
             destination[9] = (byte)monthAbbrev;
@@ -43,16 +46,16 @@ namespace System.Buffers.Text
             destination[10] = (byte)monthAbbrev;
             destination[11] = Utf8Constants.Space;
 
-            FormattingHelpers.WriteFourDecimalDigits((uint)value.Year, destination, 12);
+            FormattingHelpers.WriteFourDecimalDigits((uint)year, destination, 12);
             destination[16] = Utf8Constants.Space;
 
-            FormattingHelpers.WriteTwoDecimalDigits((uint)value.Hour, destination, 17);
+            FormattingHelpers.WriteTwoDecimalDigits((uint)hour, destination, 17);
             destination[19] = Utf8Constants.Colon;
 
-            FormattingHelpers.WriteTwoDecimalDigits((uint)value.Minute, destination, 20);
+            FormattingHelpers.WriteTwoDecimalDigits((uint)minute, destination, 20);
             destination[22] = Utf8Constants.Colon;
 
-            FormattingHelpers.WriteTwoDecimalDigits((uint)value.Second, destination, 23);
+            FormattingHelpers.WriteTwoDecimalDigits((uint)second, destination, 23);
             destination[25] = Utf8Constants.Space;
 
             destination[26] = GMT1;
index 134e83697e0e8712138dd4c6184e86b56ce57319..fdd37e347039e93d766664de2244478307249b9f 100644 (file)
@@ -97,17 +97,17 @@ namespace System
             {
                 DateTime dt = new DateTime(ticks);
 
-                int year, month, day;
-                dt.GetDatePart(out year, out month, out day);
+                dt.GetDate(out int year, out int month, out int day);
+                dt.GetTime(out int hour, out int minute, out int second, out int millisecond);
 
                 systemTime.Year = (ushort)year;
                 systemTime.Month = (ushort)month;
                 systemTime.DayOfWeek = (ushort)dt.DayOfWeek;
                 systemTime.Day = (ushort)day;
-                systemTime.Hour = (ushort)dt.Hour;
-                systemTime.Minute = (ushort)dt.Minute;
-                systemTime.Second = (ushort)dt.Second;
-                systemTime.Milliseconds = (ushort)dt.Millisecond;
+                systemTime.Hour = (ushort)hour;
+                systemTime.Minute = (ushort)minute;
+                systemTime.Second = (ushort)second;
+                systemTime.Milliseconds = (ushort)millisecond;
                 hundredNanoSecond = 0;
             }
         }
index 8452e89c071efd50f0694ee1f149c52b588b627c..a03da9563f8b69730d46f7813520327fa64f4e43 100644 (file)
@@ -509,7 +509,7 @@ namespace System
         public DateTime AddMonths(int months)
         {
             if (months < -120000 || months > 120000) throw new ArgumentOutOfRangeException(nameof(months), SR.ArgumentOutOfRange_DateTimeBadMonths);
-            GetDatePart(out int y, out int m, out int d);
+            GetDate(out int y, out int m, out int d);
             int i = m - 1 + months;
             if (i >= 0)
             {
@@ -935,10 +935,10 @@ namespace System
             return n - days[m - 1] + 1;
         }
 
-        // Exactly the same as GetDatePart(int part), except computing all of
-        // year/month/day rather than just one of them.  Used when all three
+        // Exactly the same as GetDatePart, except computing all of
+        // year/month/day rather than just one of them. Used when all three
         // are needed rather than redoing the computations for each.
-        internal void GetDatePart(out int year, out int month, out int day)
+        internal void GetDate(out int year, out int month, out int day)
         {
             long ticks = InternalTicks;
             // n = number of days since 1/1/0001
@@ -980,6 +980,42 @@ namespace System
             day = n - days[m - 1] + 1;
         }
 
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        internal void GetTime(out int hour, out int minute, out int second)
+        {
+            long n = InternalTicks / TicksPerSecond;
+            n = Math.DivRem(n, 60, out long m);
+            second = (int)m;
+            n = Math.DivRem(n, 60, out m);
+            minute = (int)m;
+            hour = (int)(n % 24);
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        internal void GetTime(out int hour, out int minute, out int second, out int millisecond)
+        {
+            long n = InternalTicks / TicksPerMillisecond;
+            n = Math.DivRem(n, 1000, out long m);
+            millisecond = (int)m;
+            n = Math.DivRem(n, 60, out m);
+            second = (int)m;
+            n = Math.DivRem(n, 60, out m);
+            minute = (int)m;
+            hour = (int)(n % 24);
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        internal void GetTimePrecise(out int hour, out int minute, out int second, out int tick)
+        {
+            long n = Math.DivRem(InternalTicks, TicksPerSecond, out long m);
+            tick = (int)m;
+            n = Math.DivRem(n, 60, out m);
+            second = (int)m;
+            n = Math.DivRem(n, 60, out m);
+            minute = (int)m;
+            hour = (int)(n % 24);
+        }
+
         // Returns the day-of-month part of this DateTime. The returned
         // value is an integer between 1 and 31.
         //
index 8f8944bba2ff55e4bb64771db00e14c4504c531b..1941e75117592e906b21f24ea4c86eeb48838e50 100644 (file)
@@ -1154,38 +1154,45 @@ namespace System
             // Hoist most of the bounds checks on destination.
             { _ = destination[MinimumBytesNeeded - 1]; }
 
-            WriteFourDecimalDigits((uint)dateTime.Year, destination, 0);
+            dateTime.GetDate(out int year, out int month, out int day);
+            dateTime.GetTimePrecise(out int hour, out int minute, out int second, out int tick);
+
+            WriteFourDecimalDigits((uint)year, destination, 0);
             destination[4] = '-';
-            WriteTwoDecimalDigits((uint)dateTime.Month, destination, 5);
+            WriteTwoDecimalDigits((uint)month, destination, 5);
             destination[7] = '-';
-            WriteTwoDecimalDigits((uint)dateTime.Day, destination, 8);
+            WriteTwoDecimalDigits((uint)day, destination, 8);
             destination[10] = 'T';
-            WriteTwoDecimalDigits((uint)dateTime.Hour, destination, 11);
+            WriteTwoDecimalDigits((uint)hour, destination, 11);
             destination[13] = ':';
-            WriteTwoDecimalDigits((uint)dateTime.Minute, destination, 14);
+            WriteTwoDecimalDigits((uint)minute, destination, 14);
             destination[16] = ':';
-            WriteTwoDecimalDigits((uint)dateTime.Second, destination, 17);
+            WriteTwoDecimalDigits((uint)second, destination, 17);
             destination[19] = '.';
-            WriteDigits((uint)((ulong)dateTime.Ticks % (ulong)TimeSpan.TicksPerSecond), destination.Slice(20, 7));
+            WriteDigits((uint)tick, destination.Slice(20, 7));
 
             if (kind == DateTimeKind.Local)
             {
+                int offsetTotalMinutes = (int)(offset.Ticks / TimeSpan.TicksPerMinute);
+
                 char sign;
-                if (offset < default(TimeSpan) /* a "const" version of TimeSpan.Zero */)
+                if (offsetTotalMinutes < 0)
                 {
                     sign = '-';
-                    offset = TimeSpan.FromTicks(-offset.Ticks);
+                    offsetTotalMinutes = -offsetTotalMinutes;
                 }
                 else
                 {
                     sign = '+';
                 }
 
+                int offsetHours = Math.DivRem(offsetTotalMinutes, 60, out int offsetMinutes);
+
                 // Writing the value backward allows the JIT to optimize by
                 // performing a single bounds check against buffer.
-                WriteTwoDecimalDigits((uint)offset.Minutes, destination, 31);
+                WriteTwoDecimalDigits((uint)offsetMinutes, destination, 31);
                 destination[30] = ':';
-                WriteTwoDecimalDigits((uint)offset.Hours, destination, 28);
+                WriteTwoDecimalDigits((uint)offsetHours, destination, 28);
                 destination[27] = sign;
             }
             else if (kind == DateTimeKind.Utc)
@@ -1216,7 +1223,8 @@ namespace System
                 dateTime -= offset;
             }
 
-            dateTime.GetDatePart(out int year, out int month, out int day);
+            dateTime.GetDate(out int year, out int month, out int day);
+            dateTime.GetTime(out int hour, out int minute, out int second);
 
             string dayAbbrev = InvariantAbbreviatedDayNames[(int)dateTime.DayOfWeek];
             Debug.Assert(dayAbbrev.Length == 3);
@@ -1237,11 +1245,11 @@ namespace System
             destination[11] = ' ';
             WriteFourDecimalDigits((uint)year, destination, 12);
             destination[16] = ' ';
-            WriteTwoDecimalDigits((uint)dateTime.Hour, destination, 17);
+            WriteTwoDecimalDigits((uint)hour, destination, 17);
             destination[19] = ':';
-            WriteTwoDecimalDigits((uint)dateTime.Minute, destination, 20);
+            WriteTwoDecimalDigits((uint)minute, destination, 20);
             destination[22] = ':';
-            WriteTwoDecimalDigits((uint)dateTime.Second, destination, 23);
+            WriteTwoDecimalDigits((uint)second, destination, 23);
             destination[25] = ' ';
             destination[26] = 'G';
             destination[27] = 'M';
index 113fec1a7d6c2692ba024a690d4042991597f0fd..459f4a74022f1802722d5f5f8cf30b42b667ce4b 100644 (file)
@@ -400,7 +400,8 @@ namespace System.Globalization
         private DateTime LunarToTime(DateTime time, int year, int month, int day)
         {
             LunarToGregorian(year, month, day, out int gy, out int gm, out int gd);
-            return GregorianCalendar.GetDefaultInstance().ToDateTime(gy, gm, gd, time.Hour, time.Minute, time.Second, time.Millisecond);
+            time.GetTime(out int hour, out int minute, out int second, out int millisecond);
+            return GregorianCalendar.GetDefaultInstance().ToDateTime(gy, gm, gd, hour, minute, second, millisecond);
         }
 
         private void TimeToLunar(DateTime time, out int year, out int month, out int day)
index 6767b9a26a293c3439828dfa4acf356d4da0d720..d0b83ccec812f2188e668d4dc20b83129c924d91 100644 (file)
@@ -145,7 +145,7 @@ namespace System.Globalization
                     SR.Format(SR.ArgumentOutOfRange_Range, -120000, 120000));
             }
 
-            time.GetDatePart(out int y, out int m, out int d);
+            time.GetDate(out int y, out int m, out int d);
             int i = m - 1 + months;
             if (i >= 0)
             {
index 477c1ed8d93c95b00d2d8df579eecf26bddb92e7..e2d8e72140e287bde9e6aba43599278e3f9388ab 100644 (file)
@@ -449,7 +449,7 @@ namespace System.Globalization
             DateTime time = new DateTime(ticks);
 
             // Save the Gregorian date values.
-            time.GetDatePart(out gregorianYear, out gregorianMonth, out gregorianDay);
+            time.GetDate(out gregorianYear, out gregorianMonth, out gregorianDay);
 
             DateBuffer lunarDate = new DateBuffer();    // lunar month and day for Jan 1
 
index ae5820841e48c6d88f10ba7953ee24b6834ab47a..05e258faafd3a2e39067f4394b06efc818602500 100644 (file)
@@ -282,7 +282,7 @@ namespace System.Globalization
             }
 
             dt = dt.AddDays(nDays);
-            dt.GetDatePart(out yg, out mg, out dg);
+            dt.GetDate(out yg, out mg, out dg);
         }
 
         private static long GetAbsoluteDateUmAlQura(int year, int month, int day)
index 7365549e975246140f3456d5ef0e7b70263bacf1..df3d3ce5060f27267abc9ce5ce7752c798b83abc 100644 (file)
@@ -119,8 +119,8 @@ namespace System
             //
             internal bool IsStartDateMarkerForBeginningOfYear() =>
                 !NoDaylightTransitions &&
-                DaylightTransitionStart.Month == 1 && DaylightTransitionStart.Day == 1 && DaylightTransitionStart.TimeOfDay.Hour == 0 &&
-                DaylightTransitionStart.TimeOfDay.Minute == 0 && DaylightTransitionStart.TimeOfDay.Second == 0 &&
+                DaylightTransitionStart.Month == 1 && DaylightTransitionStart.Day == 1 &&
+                DaylightTransitionStart.TimeOfDay.TimeOfDay.Ticks < TimeSpan.TicksPerSecond && // < 12:00:01 AM
                 _dateStart.Year == _dateEnd.Year;
 
             //
@@ -129,8 +129,8 @@ namespace System
             //
             internal bool IsEndDateMarkerForEndOfYear() =>
                 !NoDaylightTransitions &&
-                DaylightTransitionEnd.Month == 1 && DaylightTransitionEnd.Day == 1 && DaylightTransitionEnd.TimeOfDay.Hour == 0 &&
-                DaylightTransitionEnd.TimeOfDay.Minute == 0 && DaylightTransitionEnd.TimeOfDay.Second == 0 &&
+                DaylightTransitionEnd.Month == 1 && DaylightTransitionEnd.Day == 1 &&
+                DaylightTransitionEnd.TimeOfDay.TimeOfDay.Ticks < TimeSpan.TicksPerSecond && // < 12:00:01 AM
                 _dateStart.Year == _dateEnd.Year;
 
             /// <summary>
index 141b621f5cfa88a92fdc59f348a36a014aea9365..4d1a93e7ce4e924885c1651d69c09997dd744161 100644 (file)
@@ -99,7 +99,7 @@ namespace System
                     throw new ArgumentOutOfRangeException(nameof(dayOfWeek), SR.ArgumentOutOfRange_DayOfWeek);
                 }
 
-                timeOfDay.GetDatePart(out int timeOfDayYear, out int timeOfDayMonth, out int timeOfDayDay);
+                timeOfDay.GetDate(out int timeOfDayYear, out int timeOfDayMonth, out int timeOfDayDay);
                 if (timeOfDayYear != 1 || timeOfDayMonth != 1 || timeOfDayDay != 1 || (timeOfDay.Ticks % TimeSpan.TicksPerMillisecond != 0))
                 {
                     throw new ArgumentException(SR.Argument_DateTimeHasTicks, nameof(timeOfDay));
index 9aad352304dc3a4e34b6c4c21a614dc316d4d137..af68566a1964b737c2e1678e765ff387f08c5fc7 100644 (file)
@@ -1751,17 +1751,24 @@ namespace System
         internal static DateTime TransitionTimeToDateTime(int year, TransitionTime transitionTime)
         {
             DateTime value;
-            DateTime timeOfDay = transitionTime.TimeOfDay;
+            TimeSpan timeOfDay = transitionTime.TimeOfDay.TimeOfDay;
 
             if (transitionTime.IsFixedDateRule)
             {
                 // create a DateTime from the passed in year and the properties on the transitionTime
 
+                int day = transitionTime.Day;
                 // if the day is out of range for the month then use the last day of the month
-                int day = DateTime.DaysInMonth(year, transitionTime.Month);
+                if (day > 28)
+                {
+                    int daysInMonth = DateTime.DaysInMonth(year, transitionTime.Month);
+                    if (day > daysInMonth)
+                    {
+                        day = daysInMonth;
+                    }
+                }
 
-                value = new DateTime(year, transitionTime.Month, (day < transitionTime.Day) ? day : transitionTime.Day,
-                            timeOfDay.Hour, timeOfDay.Minute, timeOfDay.Second, timeOfDay.Millisecond);
+                value = new DateTime(year, transitionTime.Month, day) + timeOfDay;
             }
             else
             {
@@ -1770,8 +1777,7 @@ namespace System
                     //
                     // Get the (transitionTime.Week)th Sunday.
                     //
-                    value = new DateTime(year, transitionTime.Month, 1,
-                            timeOfDay.Hour, timeOfDay.Minute, timeOfDay.Second, timeOfDay.Millisecond);
+                    value = new DateTime(year, transitionTime.Month, 1) + timeOfDay;
 
                     int dayOfWeek = (int)value.DayOfWeek;
                     int delta = (int)transitionTime.DayOfWeek - dayOfWeek;
@@ -1792,8 +1798,7 @@ namespace System
                     // If TransitionWeek is greater than 4, we will get the last week.
                     //
                     int daysInMonth = DateTime.DaysInMonth(year, transitionTime.Month);
-                    value = new DateTime(year, transitionTime.Month, daysInMonth,
-                            timeOfDay.Hour, timeOfDay.Minute, timeOfDay.Second, timeOfDay.Millisecond);
+                    value = new DateTime(year, transitionTime.Month, daysInMonth) + timeOfDay;
 
                     // This is the day of week for the last day of the month.
                     int dayOfWeek = (int)value.DayOfWeek;