Created
May 4, 2019 00:31
-
-
Save tarekgh/4f038db754a621fb38b17cf9fc7c0b3d to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Licensed to the .NET Foundation under one or more agreements. | |
// The .NET Foundation licenses this file to you under the MIT license. | |
// See the LICENSE file in the project root for more information. | |
namespace System | |
{ | |
// | |
// - SystemDateTime is representing the system Date and Time. it is tied to the running OS behavior. | |
// - It can handle leap seconds, i.e. when there is a leap second in the system, it can report it as second 60. | |
// - When subtracting 2 SystemDateTime instances, the leap seconds will be accounted in resulted TimeSpan. | |
// - It depends on if the app opted-in in to leaps seconds to report the leap second as 59 or as 60 | |
// - If using leap second values (i.e. 60) on systems not supporting leap seconds that can cause exception throwing. | |
// - If you serialize SystemDateTime fields from one system to another, this may cause some behavior differences depending on | |
// if the systems support the leap seconds, and the defined leap seconds in such systems are same or not. | |
// | |
public unsafe struct SystemDateTime : IComparable, IComparable<SystemDateTime> | |
{ | |
// | |
// Constructors | |
// | |
public SystemDateTime(int year, int month, int day, int hour, int minute, int second, bool isLocalTime = false) : | |
this(year, month, day, hour, minute, second, 0, isLocalTime) | |
{ } | |
public SystemDateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, bool isLocalTime = false) : | |
this(year, month, day, hour, minute, second, millisecond, 0, isLocalTime) | |
{ } | |
public SystemDateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, int fraction, bool isLocalTime = false) | |
{ | |
if (year < 1 || year > 9999 || | |
month < 1 || month > 12 || | |
day < 1 || day > 31 || | |
hour < 0 || hour > 23 || | |
minute < 0 || minute > 59 || | |
second < 0 || second > 60 || | |
millisecond < 0 || millisecond > 999 || | |
fraction < 0 || fraction > 9999) | |
{ | |
throw new ArgumentException("Invalid Time Unit"); | |
} | |
fixed (SYSTEMTIME* pst = &_time) | |
{ | |
*pst = new SYSTEMTIME | |
{ | |
wYear = (ushort)year, | |
wMonth = (ushort)month, | |
wDay = (ushort)day, | |
wHour = (ushort)hour, | |
wMinute = (ushort)minute, | |
wSecond = (ushort)second, | |
wMilliseconds = (ushort)millisecond | |
}; | |
// validate the input time | |
if (isLocalTime) | |
{ | |
if (TimeZoneInfo.Local.IsInvalidTime(new DateTime(year, month, day, hour, minute, second, DateTimeKind.Local))) | |
{ | |
throw new ArgumentException("Invalid local time input"); | |
} | |
//SYSTEMTIME st = new SYSTEMTIME(); | |
//if (!TzSpecificLocalTimeToSystemTime(IntPtr.Zero, pst, &st)) | |
//{ | |
// throw new ArgumentException("Invalid local time input"); | |
//} | |
} | |
else | |
{ | |
long fileTime; | |
if (!SystemTimeToFileTime(pst, &fileTime)) | |
{ | |
throw new ArgumentException("Invalid UTC time input"); | |
} | |
} | |
} | |
_time.wDayOfWeek = (ushort)new DateTime(year, month, day).DayOfWeek; | |
_fraction = (ushort)fraction; | |
_flags = isLocalTime ? Flags.Local : Flags.Utc; | |
} | |
private SystemDateTime(long fileTimeTicks) | |
{ | |
_time = new SYSTEMTIME(); | |
fixed (SYSTEMTIME* pst = &_time) | |
{ | |
if (!FileTimeToSystemTime(&fileTimeTicks, pst)) | |
{ | |
throw new ArgumentException("Invalid File Time"); | |
} | |
} | |
_fraction = (ushort)(fileTimeTicks % HundredNanoSecondsInMillisecond); | |
_flags = Flags.Utc; | |
} | |
// | |
// Properties | |
// | |
public int Year { get { return _time.wYear; } } | |
public int Month { get { return _time.wMonth; } } | |
public int Day { get { return _time.wDay; } } | |
public int Hour { get { return _time.wHour; } } | |
public int Minute { get { return _time.wMinute; } } | |
public int Second { get { return _time.wSecond; } } // 60 can be reported for the leap seconds | |
public int Millisecond { get { return _time.wMilliseconds; } } | |
public int Fraction { get { return _fraction; } } | |
public DayOfWeek DayOfWeek { get { return (DayOfWeek)_time.wDayOfWeek; } } | |
public bool IsLocal { get { return (_flags & Flags.Local) != 0; } } | |
public static bool EnableLeapSeconds | |
{ | |
get | |
{ | |
PROCESS_LEAP_SECOND_INFO plsi = new PROCESS_LEAP_SECOND_INFO(); | |
if (GetProcessInformation(GetCurrentProcess(), ProcessLeapSecondInfo, ref plsi, 8)) | |
{ | |
return ((plsi.Flags & PROCESS_LEAP_SECOND_INFO_FLAG_ENABLE_SIXTY_SECOND) != 0); | |
} | |
return false; | |
} | |
set | |
{ | |
if (value && !s_isSupportedLeapSecondsSystem) | |
{ | |
throw new InvalidOperationException("The current running system is not enabled to support leap seconds."); | |
} | |
PROCESS_LEAP_SECOND_INFO plsi = new PROCESS_LEAP_SECOND_INFO(); | |
if (value) | |
plsi.Flags = PROCESS_LEAP_SECOND_INFO_FLAG_ENABLE_SIXTY_SECOND; | |
if (!SetProcessInformation(GetCurrentProcess(), ProcessLeapSecondInfo, ref plsi, 8)) | |
throw new InvalidOperationException("SetProcessInformation Failed."); | |
} | |
} | |
public static bool IsLeapSecondsEnabledSystem { get { return s_isSupportedLeapSecondsSystem; } } | |
// | |
// Methods | |
// | |
public static SystemDateTime GetCurrentUtcTime() | |
{ | |
long systemTimeAsFileTime; | |
GetSystemTimeAsFileTime(&systemTimeAsFileTime); | |
return new SystemDateTime(systemTimeAsFileTime); | |
} | |
public static SystemDateTime GetCurrentLocalTime() | |
{ | |
SystemDateTime st = GetCurrentUtcTime(); | |
SystemDateTime local = new SystemDateTime(); | |
if (!SystemTimeToTzSpecificLocalTime(IntPtr.Zero, &st._time, &local._time)) | |
{ | |
throw new ArgumentException("Cannot get the local time"); | |
} | |
local._fraction = (ushort)st.Fraction; | |
local._flags = Flags.Local; | |
return local; | |
} // Should we continue use Now property instead? | |
public int CompareTo(SystemDateTime value) | |
{ | |
return (int)(ToFileTime() - value.ToFileTime()); | |
} | |
public int CompareTo(Object value) | |
{ | |
if (value is SystemDateTime) | |
return CompareTo((SystemDateTime)value); | |
throw new ArgumentException("Argument should be of type SystemDateTime."); | |
} | |
public static SystemDateTime FromFileTime(long fileTime) | |
{ | |
SystemDateTime sdt = FromFileTimeUtc(fileTime); | |
SystemDateTime local = new SystemDateTime(); | |
if (!SystemTimeToTzSpecificLocalTime(IntPtr.Zero, &sdt._time, &local._time)) | |
{ | |
throw new ArgumentException("Invalid input time"); | |
} | |
local._flags = Flags.Local; | |
local._fraction = (ushort)sdt.Fraction; | |
return local; | |
} | |
public static SystemDateTime FromFileTimeUtc(long fileTime) | |
{ | |
return new SystemDateTime(fileTime); | |
} | |
public long ToFileTime() | |
{ | |
long fileTime; | |
fixed (SYSTEMTIME* pst = &_time) | |
{ | |
if (IsLocal) | |
{ | |
SYSTEMTIME st = new SYSTEMTIME(); | |
if (!TzSpecificLocalTimeToSystemTime(IntPtr.Zero, pst, &st)) | |
{ | |
throw new ArgumentException("Invalid local time input"); | |
} | |
SystemTimeToFileTime(&st, &fileTime); | |
} | |
else | |
{ | |
SystemTimeToFileTime(pst, &fileTime); | |
} | |
} | |
return fileTime + Fraction; | |
} | |
public bool Equals(SystemDateTime value) | |
{ | |
return CompareTo(value) == 0; | |
} | |
public override bool Equals(object value) | |
{ | |
if (value is SystemDateTime) | |
return Equals((SystemDateTime)value); | |
throw new ArgumentException("value has to be of type SystemDateTime"); | |
} | |
public override int GetHashCode() | |
{ | |
long fileTime = ToFileTime(); | |
return unchecked((int)fileTime) ^ (int)(fileTime >> 32); | |
} | |
public override string ToString() | |
{ | |
return ToString(null, null); | |
} | |
public string ToString(string format) | |
{ | |
return ToString(format, null); | |
} | |
public string ToString(IFormatProvider provider) | |
{ | |
return ToString(null, provider); | |
} | |
public string ToString(string format, IFormatProvider provider) | |
{ | |
DateTime dt = IsLocal ? DateTime.FromFileTime(ToFileTime()) : DateTime.FromFileTimeUtc(ToFileTime()); | |
string formattedString = dt.ToString(format, provider); | |
if (Second == 60) | |
{ | |
int index = formattedString.IndexOf("59", StringComparison.Ordinal); | |
if (index >= 0) | |
{ | |
string formatted1 = dt.AddSeconds(-1).ToString(format, provider); | |
if (formatted1.Length == formattedString.Length) | |
{ | |
for (int i = index; i < formattedString.Length - 1; i++) | |
{ | |
if (formattedString[i] == '5' && formattedString[i] == formatted1[i] && formattedString[i + 1] == '9' && formatted1[i + 1] == '8') | |
{ | |
return formattedString.Substring(0, i) + "60" + formattedString.Substring(i + 2); | |
} | |
} | |
} | |
} | |
} | |
return formattedString; | |
// return $"{DayOfWeek} {Year:D4}-{Month:D2}-{Day:D2}T{Hour:D2}:{Minute:D2}:{Second:D2}:{Millisecond:D3}.{Fraction:D4} " + (IsLocal ? "Local" : "Utc"); | |
} | |
// | |
// Operators | |
// | |
public static bool operator ==(SystemDateTime d1, SystemDateTime d2) | |
{ | |
return d1.Equals(d2); | |
} | |
public static bool operator !=(SystemDateTime d1, SystemDateTime d2) | |
{ | |
return !d1.Equals(d2); | |
} | |
public static bool operator >(SystemDateTime d1, SystemDateTime d2) | |
{ | |
return d1.ToFileTime() > d2.ToFileTime(); | |
} | |
public static bool operator >=(SystemDateTime d1, SystemDateTime d2) | |
{ | |
return d1.ToFileTime() >= d2.ToFileTime(); | |
} | |
public static bool operator <(SystemDateTime d1, SystemDateTime d2) | |
{ | |
return d1.ToFileTime() < d2.ToFileTime(); | |
} | |
public static bool operator <=(SystemDateTime d1, SystemDateTime d2) | |
{ | |
return d1.ToFileTime() <= d2.ToFileTime(); | |
} | |
public static TimeSpan operator -(SystemDateTime d1, SystemDateTime d2) | |
{ | |
return new TimeSpan(d1.ToFileTime() - d2.ToFileTime()); | |
} | |
// | |
// Private/internal stuff | |
// | |
private SYSTEMTIME _time; | |
private ushort _fraction; // 0 .. 9999 | |
private Flags _flags; // Utc, Local (0 or 1) | |
private static bool s_isSupportedLeapSecondsSystem = IsLeapSecondsSupportedSystem(); | |
private const int HundredNanoSecondsInMillisecond = 10_000; | |
private const int ProcessLeapSecondInfo = 8; | |
private const uint PROCESS_LEAP_SECOND_INFO_FLAG_ENABLE_SIXTY_SECOND = 0x1; | |
private const int SystemLeapSecondInformation = 206; | |
private static bool IsLeapSecondsSupportedSystem() | |
{ | |
SYSTEM_LEAP_SECOND_INFORMATION slsi = new SYSTEM_LEAP_SECOND_INFORMATION(); | |
return NtQuerySystemInformation(SystemLeapSecondInformation, &slsi, sizeof(SYSTEM_LEAP_SECOND_INFORMATION), IntPtr.Zero) == 0 && slsi.Enabled; | |
} | |
[StructLayout(LayoutKind.Sequential)] | |
private struct SYSTEM_LEAP_SECOND_INFORMATION | |
{ | |
public bool Enabled; | |
public uint Flags; | |
} | |
[StructLayout(LayoutKind.Sequential)] | |
private struct SYSTEMTIME | |
{ | |
public ushort wYear; | |
public ushort wMonth; | |
public ushort wDayOfWeek; | |
public ushort wDay; | |
public ushort wHour; | |
public ushort wMinute; | |
public ushort wSecond; | |
public ushort wMilliseconds; | |
} | |
[StructLayout(LayoutKind.Sequential)] | |
internal struct PROCESS_LEAP_SECOND_INFO | |
{ | |
internal uint Flags; | |
internal uint Reserved; | |
}; | |
[Flags] | |
private enum Flags : byte | |
{ | |
Utc = 0x00, | |
Local = 0x01, | |
} | |
[DllImport("ntdll.dll", EntryPoint = "NtQuerySystemInformation", SetLastError = true)] | |
private static extern int NtQuerySystemInformation(int SystemInformationClass, void* SystemInformation, int SystemInformationLength, IntPtr ReturnLength); | |
[DllImport("kernel32.dll", EntryPoint = "GetSystemTimeAsFileTime", SetLastError = true)] | |
private static unsafe extern void GetSystemTimeAsFileTime(long* lpSystemTimeAsFileTime); | |
[DllImport("kernel32.dll", EntryPoint = "FileTimeToSystemTime", SetLastError = true)] | |
private static unsafe extern bool FileTimeToSystemTime(long* lpFileTime, SYSTEMTIME* lpSystemTime); | |
[DllImport("kernel32.dll", EntryPoint = "SystemTimeToFileTime", SetLastError = true)] | |
private static unsafe extern bool SystemTimeToFileTime(SYSTEMTIME* lpSystemTime, long* lpFileTime); | |
[DllImport("kernel32.dll", EntryPoint = "TzSpecificLocalTimeToSystemTime", SetLastError = true)] | |
private static unsafe extern bool TzSpecificLocalTimeToSystemTime(IntPtr lpTimeZoneInformation, SYSTEMTIME* lpLocalTime, SYSTEMTIME* lpUniversalTime); | |
[DllImport("kernel32.dll", EntryPoint = "SystemTimeToTzSpecificLocalTime", SetLastError = true)] | |
private static unsafe extern bool SystemTimeToTzSpecificLocalTime(IntPtr lpTimeZone, SYSTEMTIME* lpUniversalTime, SYSTEMTIME* lpLocalTime); | |
[DllImport("kernel32.dll")] | |
private static extern IntPtr GetCurrentProcess(); | |
[DllImport("kernel32.dll")] | |
private static extern bool SetProcessInformation(IntPtr hProcess, int ProcessInformationClass, ref PROCESS_LEAP_SECOND_INFO ProcessInformation, int ProcessInformationSize); | |
[DllImport("kernel32.dll")] | |
private static extern bool GetProcessInformation(IntPtr hProcess, int ProcessInformationClass, ref PROCESS_LEAP_SECOND_INFO ProcessInformation, int ProcessInformationSize); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment