Skip to content

Instantly share code, notes, and snippets.

@tarekgh
Created May 4, 2019 00:31
Show Gist options
  • Save tarekgh/4f038db754a621fb38b17cf9fc7c0b3d to your computer and use it in GitHub Desktop.
Save tarekgh/4f038db754a621fb38b17cf9fc7c0b3d to your computer and use it in GitHub Desktop.
// 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