1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
5 using System
.Runtime
.CompilerServices
;
6 using System
.Runtime
.InteropServices
;
10 public readonly partial struct DateTime
12 internal static readonly bool s_systemSupportsLeapSeconds
= SystemSupportsLeapSeconds();
14 public static unsafe DateTime UtcNow
18 if (s_systemSupportsLeapSeconds
)
21 GetSystemTimeWithLeapSecondsHandling(&time
);
22 return CreateDateTimeFromSystemTime(in time
);
25 return new DateTime(((ulong)(GetSystemTimeAsFileTime() + FileTimeOffset
)) | KindUtc
);
29 internal static unsafe bool IsValidTimeWithLeapSeconds(int year
, int month
, int day
, int hour
, int minute
, int second
, DateTimeKind kind
)
31 DateTime dt
= new DateTime(year
, month
, day
);
32 FullSystemTime time
= new FullSystemTime(year
, month
, dt
.DayOfWeek
, day
, hour
, minute
, second
);
36 DateTimeKind
.Local
=> ValidateSystemTime(&time
.systemTime
, localTime
: true),
37 DateTimeKind
.Utc
=> ValidateSystemTime(&time
.systemTime
, localTime
: false),
38 _
=> ValidateSystemTime(&time
.systemTime
, localTime
: true) || ValidateSystemTime(&time
.systemTime
, localTime
: false),
42 private static unsafe DateTime
FromFileTimeLeapSecondsAware(long fileTime
)
45 if (FileTimeToSystemTime(fileTime
, &time
))
47 return CreateDateTimeFromSystemTime(in time
);
50 throw new ArgumentOutOfRangeException(nameof(fileTime
), SR
.ArgumentOutOfRange_DateTimeBadTicks
);
53 private static unsafe long ToFileTimeLeapSecondsAware(long ticks
)
55 FullSystemTime time
= new FullSystemTime(ticks
);
58 if (SystemTimeToFileTime(&time
.systemTime
, &fileTime
))
60 return fileTime
+ ticks
% TicksPerMillisecond
;
63 throw new ArgumentOutOfRangeException(null, SR
.ArgumentOutOfRange_FileTimeInvalid
);
66 [MethodImpl(MethodImplOptions
.AggressiveInlining
)]
67 private static DateTime
CreateDateTimeFromSystemTime(in FullSystemTime time
)
69 long ticks
= DateToTicks(time
.systemTime
.Year
, time
.systemTime
.Month
, time
.systemTime
.Day
);
70 ticks
+= TimeToTicks(time
.systemTime
.Hour
, time
.systemTime
.Minute
, time
.systemTime
.Second
);
71 ticks
+= time
.systemTime
.Milliseconds
* TicksPerMillisecond
;
72 ticks
+= time
.hundredNanoSecond
;
73 return new DateTime(((ulong)(ticks
)) | KindUtc
);
76 // FullSystemTime struct is the SYSTEMTIME struct with extra hundredNanoSecond field to store more precise time.
77 [StructLayout(LayoutKind
.Sequential
)]
78 private struct FullSystemTime
80 internal Interop
.Kernel32
.SYSTEMTIME systemTime
;
81 internal long hundredNanoSecond
;
83 internal FullSystemTime(int year
, int month
, DayOfWeek dayOfWeek
, int day
, int hour
, int minute
, int second
)
85 systemTime
.Year
= (ushort)year
;
86 systemTime
.Month
= (ushort)month
;
87 systemTime
.DayOfWeek
= (ushort)dayOfWeek
;
88 systemTime
.Day
= (ushort)day
;
89 systemTime
.Hour
= (ushort)hour
;
90 systemTime
.Minute
= (ushort)minute
;
91 systemTime
.Second
= (ushort)second
;
92 systemTime
.Milliseconds
= 0;
93 hundredNanoSecond
= 0;
96 internal FullSystemTime(long ticks
)
98 DateTime dt
= new DateTime(ticks
);
100 int year
, month
, day
;
101 dt
.GetDatePart(out year
, out month
, out day
);
103 systemTime
.Year
= (ushort)year
;
104 systemTime
.Month
= (ushort)month
;
105 systemTime
.DayOfWeek
= (ushort)dt
.DayOfWeek
;
106 systemTime
.Day
= (ushort)day
;
107 systemTime
.Hour
= (ushort)dt
.Hour
;
108 systemTime
.Minute
= (ushort)dt
.Minute
;
109 systemTime
.Second
= (ushort)dt
.Second
;
110 systemTime
.Milliseconds
= (ushort)dt
.Millisecond
;
111 hundredNanoSecond
= 0;
116 internal static readonly bool s_systemSupportsPreciseSystemTime
= SystemSupportsPreciseSystemTime();
118 private static unsafe bool SystemSupportsPreciseSystemTime()
120 if (Environment
.IsWindows8OrAbove
)
122 // GetSystemTimePreciseAsFileTime exists and we'd like to use it. However, on
123 // misconfigured systems, it's possible for the "precise" time to be inaccurate:
124 // https://github.com/dotnet/coreclr/issues/14187
125 // If it's inaccurate, though, we expect it to be wildly inaccurate, so as a
126 // workaround/heuristic, we get both the "normal" and "precise" times, and as
127 // long as they're close, we use the precise one. This workaround can be removed
128 // when we better understand what's causing the drift and the issue is no longer
129 // a problem or can be better worked around on all targeted OSes.
131 long systemTimeResult
;
132 Interop
.Kernel32
.GetSystemTimeAsFileTime(&systemTimeResult
);
134 long preciseSystemTimeResult
;
135 Interop
.Kernel32
.GetSystemTimePreciseAsFileTime(&preciseSystemTimeResult
);
137 return Math
.Abs(preciseSystemTimeResult
- systemTimeResult
) <= 100 * TicksPerMillisecond
;
143 private static unsafe bool ValidateSystemTime(Interop
.Kernel32
.SYSTEMTIME
* time
, bool localTime
)
147 Interop
.Kernel32
.SYSTEMTIME st
;
148 return Interop
.Kernel32
.TzSpecificLocalTimeToSystemTime(IntPtr
.Zero
, time
, &st
) != Interop
.BOOL
.FALSE
;
153 return Interop
.Kernel32
.SystemTimeToFileTime(time
, ×tamp
) != Interop
.BOOL
.FALSE
;
157 private static unsafe bool FileTimeToSystemTime(long fileTime
, FullSystemTime
* time
)
159 if (Interop
.Kernel32
.FileTimeToSystemTime(&fileTime
, &time
->systemTime
) != Interop
.BOOL
.FALSE
)
161 // to keep the time precision
162 time
->hundredNanoSecond
= fileTime
% TicksPerMillisecond
;
163 if (time
->systemTime
.Second
> 59)
165 // we have a leap second, force it to last second in the minute as DateTime doesn't account for leap seconds in its calculation.
166 // we use the maxvalue from the milliseconds and the 100-nano seconds to avoid reporting two out of order 59 seconds
167 time
->systemTime
.Second
= 59;
168 time
->systemTime
.Milliseconds
= 999;
169 time
->hundredNanoSecond
= 9999;
176 private static unsafe void GetSystemTimeWithLeapSecondsHandling(FullSystemTime
* time
)
178 if (!FileTimeToSystemTime(GetSystemTimeAsFileTime(), time
))
180 Interop
.Kernel32
.GetSystemTime(&time
->systemTime
);
181 time
->hundredNanoSecond
= 0;
182 if (time
->systemTime
.Second
> 59)
184 // we have a leap second, force it to last second in the minute as DateTime doesn't account for leap seconds in its calculation.
185 // we use the maxvalue from the milliseconds and the 100-nano seconds to avoid reporting two out of order 59 seconds
186 time
->systemTime
.Second
= 59;
187 time
->systemTime
.Milliseconds
= 999;
188 time
->hundredNanoSecond
= 9999;
193 private static unsafe bool SystemTimeToFileTime(Interop
.Kernel32
.SYSTEMTIME
* time
, long* fileTime
)
195 return Interop
.Kernel32
.SystemTimeToFileTime(time
, fileTime
) != Interop
.BOOL
.FALSE
;
198 private static unsafe long GetSystemTimeAsFileTime()
202 if (s_systemSupportsPreciseSystemTime
)
204 Interop
.Kernel32
.GetSystemTimePreciseAsFileTime(×tamp
);
208 Interop
.Kernel32
.GetSystemTimeAsFileTime(×tamp
);