using System.Globalization; // GlobalDateTime.cs // v1.0 // by, Jeffery Walker // http://cs.oberlin.edu/~jwalker namespace System { public struct GlobalDateTime : IComparable, IConvertible, IFormattable { private long ticks; #region Conversion Constants private const long TickDelta = (366 + CalendarOffset)/*days*/ * 24/*hours/day*/ * 60/*min/hour*/ * 60/*s/min*/ * TimeSpan.TicksPerSecond; private const long YearsPerCycle = 400; private const long DaysPerCycle = 303 * DaysPerYear + 97 * DaysPerLeapYear; private const long TicksPerCycle = DaysPerCycle * TimeSpan.TicksPerDay; private const long YearsPerQuarterCycle = YearsPerCycle / 4; private const long DaysPerQuarterCycle = 76 * DaysPerYear + 24 * DaysPerLeapYear; private const long YearsPerCentiCycle = YearsPerCycle / 100; private const long DaysPerCentiCycle = 3 * DaysPerYear + 1 * DaysPerLeapYear; private const long DaysPerYear = 365; private const long DaysPerLeapYear = 366; #endregion public const int CalendarOffset = 10; public const DayOfWeek NewYearsDay = (DayOfWeek)(-1); public const DayOfWeek LeapDay = (DayOfWeek)7; #region Constructors public GlobalDateTime(long ticks) { this.ticks = ticks; } public GlobalDateTime(int year, int month, int day): this(year, month, day, 0, 0, 0, 0) { } public GlobalDateTime(int year, int month, int day, Calendar calendar) { this.ticks = calendar.ToDateTime(year, month, day, 0, 0, 0, 0).Ticks + TickDelta; } public GlobalDateTime(int year, int month, int day, int hour, int minute, int second): this(year, month, day, hour, minute, second, 0) { } public GlobalDateTime(int year, int month, int day, int hour, int minute, int second, Calendar calendar) { this.ticks = calendar.ToDateTime(year, month, day, hour, minute, second, 0).Ticks + TickDelta; } public GlobalDateTime(int year, int month, int day, int hour, int minute, int second, int millisecond) { //Ticks past until this year ticks = YearToTicks(year); //Check Arguments CheckMonth(month); CheckDay(year, month, day); CheckHour(hour); CheckMinute(minute); CheckSecond(second); CheckMillisecond(millisecond); // This forumla looks funny but it isn't wrong. The line for days past takes // account of Jan 0. If it is day zero than no days have past this month and // Jan 0 isn't in the past. However, if its not day zero then some days may have // past this month and also Jan 0 has past (and isn't accounted for otherwise). // So we have: // if(days>0) ticks += ( + ) * TimeSpan.TicksPerDay // which is equivalent to // if(days>0) ticks += ((day-1) + 1) * TimeSpan.TicksPerDay // which is equivalent to // if(days>0) ticks += day * TimeSpan.TicksPerDay // which is equivalent to (since day >= 0 by CheckDay above) // ticks += day * TimeSpan.TicksPerDay ticks += (month-1) * 28 * TimeSpan.TicksPerDay //months past + day * TimeSpan.TicksPerDay + hour * TimeSpan.TicksPerHour + minute * TimeSpan.TicksPerMinute + second * TimeSpan.TicksPerSecond + millisecond * TimeSpan.TicksPerMillisecond; } public GlobalDateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, Calendar calendar) { this.ticks = calendar.ToDateTime(year, month, day, hour, minute, second, millisecond).Ticks + TickDelta; } #endregion #region Fields public static readonly GlobalDateTime MaxValue = new GlobalDateTime(long.MinValue); public static readonly GlobalDateTime MinValue = new GlobalDateTime(long.MinValue); #endregion #region Properties public static GlobalDateTime Now { get { return DateTime.Now; } } public static GlobalDateTime UtcNow { get { return DateTime.UtcNow; } } public static GlobalDateTime Today { get { return DateTime.Today; } } public GlobalDateTime Date { get { return new GlobalDateTime(ticks - (ticks % TimeSpan.TicksPerDay)); } } public TimeSpan TimeOfDay { get { return new TimeSpan(ticks % TimeSpan.TicksPerDay); } } public DayOfWeek DayOfWeek { get { int dayOfYear = DayOfYear; switch(dayOfYear) { case 0: return NewYearsDay; case 266: return LeapDay; default: return (DayOfWeek)((dayOfYear-2) % 7 + 1); } } } public int DayOfYear { get { long daysLeft = ticks / TimeSpan.TicksPerDay; daysLeft %= DaysPerCycle; //The first quarter cycle has an extra day in it daysLeft = (daysLeft==0) ? 0 : daysLeft-1; daysLeft %= DaysPerQuarterCycle; //The first centi cycle is missing a day daysLeft = (daysLeft==0) ? 0 : daysLeft+1; daysLeft %= DaysPerCentiCycle; //The first year has an extra day daysLeft = (daysLeft==0) ? 0 : daysLeft-1; daysLeft %= DaysPerYear; return (int)daysLeft; } } /// /// The value of this property is the number of 100-nanosecond intervals that /// have elapsed since 0000-01-00 12:00AM (negative indicate negative year dates) /// public long Ticks { get { return ticks; } } public int Year { get { return TicksToYear(ticks); } } public int Month { get { int dayOfYear = DayOfYear; switch(dayOfYear) { case 1: return 1; case 366: return 13; default: return (dayOfYear-2)/28+1; } } } public int Day { get { int dayOfYear = DayOfYear; switch(dayOfYear) { case 1: return 0; case 366: return 29; default: return (dayOfYear-2)%28+1; } } } public int Hour { get { return (int)(ticks % TimeSpan.TicksPerDay / TimeSpan.TicksPerHour); } } public int Minute { get { return (int)(ticks % TimeSpan.TicksPerHour / TimeSpan.TicksPerMinute); } } public int Second { get { return (int)(ticks % TimeSpan.TicksPerMinute / TimeSpan.TicksPerSecond); } } public int Millisecond { get { return (int)(ticks % TimeSpan.TicksPerSecond / TimeSpan.TicksPerMillisecond); } } #endregion #region Methods public GlobalDateTime Add(TimeSpan value) { return new GlobalDateTime(ticks + value.Ticks); } public GlobalDateTime AddYears(int years) { int year = TicksToYear(ticks); long baseTicks = YearToTicks(year); int targetYear = year + years; long tickDelta = YearToTicks(targetYear) - baseTicks; if(IsLeapYear(year) && !IsLeapYear(targetYear)) { int dayOfYear = (int)((ticks - baseTicks) / TimeSpan.TicksPerDay + 1); if(dayOfYear == 366) tickDelta -= TimeSpan.TicksPerDay; } return new GlobalDateTime(ticks + tickDelta); } public GlobalDateTime AddWeeks(int weeks) { GlobalDateTime yearAdjustedDate = this; int targetDayOfYear = DayOfYear+weeks*7; if(targetDayOfYear < 1 || targetDayOfYear > DaysInYear(Year)) { int years = weeks/52; yearAdjustedDate = this.AddYears(years); weeks -= years*52; } int days = weeks * 7; if(yearAdjustedDate.DayOfYear==0) days += 1; if(yearAdjustedDate.DayOfYear==366) days -=1; return yearAdjustedDate.AddDays(days); } public GlobalDateTime AddMonths(int months) { GlobalDateTime yearAdjustedDate = this; int targetDayOfYear = DayOfYear+months*28; if(targetDayOfYear < 1 || targetDayOfYear > DaysInYear(Year)) { int years = months/13; yearAdjustedDate = this.AddYears(years); months -= years*13; } int days = months * 28; if(yearAdjustedDate.DayOfYear==0) days += 1; if(yearAdjustedDate.DayOfYear==366) days -=1; return yearAdjustedDate.AddDays(days); } public GlobalDateTime AddDays(double days) { return new GlobalDateTime(ticks + (long)(days*TimeSpan.TicksPerDay)); } public GlobalDateTime AddHours(double hours) { return new GlobalDateTime(ticks + (long)(hours*TimeSpan.TicksPerHour)); } public GlobalDateTime AddMinutes(double minutes) { return new GlobalDateTime(ticks + (long)(minutes*TimeSpan.TicksPerMinute)); } public GlobalDateTime AddSeconds(double seconds) { return new GlobalDateTime(ticks + (long)(seconds*TimeSpan.TicksPerSecond)); } public GlobalDateTime AddMilliseconds(double milliseconds) { return new GlobalDateTime(ticks + (long)(milliseconds*TimeSpan.TicksPerMillisecond)); } public GlobalDateTime AddTicks(long ticks) { return new GlobalDateTime(ticks + ticks); } public int CompareTo(object value) { if(value==null) return -1; if(value is GlobalDateTime) return GlobalDateTime.Compare(this, (GlobalDateTime)value); else throw new ArgumentException("Object must be of type GlobalDateTime."); } public override bool Equals(object value) { if(value is GlobalDateTime) { return this.ticks == ((GlobalDateTime)value).ticks; } return false; } public override int GetHashCode() { return ticks.GetHashCode(); } public System.TypeCode GetTypeCode() { //TODO: is this a correct implementation? or should be return TypeCode.Object return TypeCode.DateTime; } public TimeSpan Subtract(GlobalDateTime value) { return new TimeSpan(ticks - value.ticks); } public GlobalDateTime Subtract(TimeSpan value) { return new GlobalDateTime(ticks - value.Ticks); } public string[] GetDateTimeFormats() { throw new NotImplementedException(); } public string[] GetDateTimeFormats(char format) { throw new NotImplementedException(); } public string[] GetDateTimeFormats(IFormatProvider provider) { throw new NotImplementedException(); } public string[] GetDateTimeFormats(char format, IFormatProvider provider) { throw new NotImplementedException(); } public long ToFileTime() { return ((DateTime)this).ToFileTime(); } public long ToFileTimeUtc() { return ((DateTime)this).ToFileTimeUtc(); } public double ToOADate() { return ((DateTime)this).ToOADate(); } public GlobalDateTime ToLocalTime() { return ((DateTime)this).ToLocalTime(); } public GlobalDateTime ToUniversalTime() { return ((DateTime)this).ToUniversalTime(); } public string ToLongDateString() { throw new NotImplementedException(); } public string ToLongTimeString() { return Hour.ToString().PadLeft(2, '0') + ":" + Minute.ToString().PadLeft(2, '0') + ":" + Second.ToString().PadLeft(2, '0');; } public string ToShortDateString() { return Year.ToString().PadLeft(4, '0') + "-" + Month.ToString().PadLeft(2, '0') + "-" + Day.ToString().PadLeft(2, '0'); } public string ToShortTimeString() { return Hour.ToString().PadLeft(2, '0') + ":" + Minute.ToString().PadLeft(2, '0'); } public string ToString(string format, IFormatProvider formatProvider) { return ToString(format); } public string ToString(string format) { switch(format) { case "d": return ToShortDateString(); case "D": return ToLongDateString(); case "t": return ToShortTimeString(); case "T": return ToLongTimeString(); case "f": return ToLongDateString() + " " + ToShortTimeString(); case "F": return ToLongDateString() + " " + ToLongTimeString(); case "g": return ToShortDateString() + " " + ToShortTimeString(); case "G": return ToString(); case "m": case "M": throw new NotImplementedException(); case "r": case "R": throw new NotImplementedException(); case "s": return ToLongDateString() + "T" + ToLongDateString(); case "u": throw new NotImplementedException(); case "U": throw new NotImplementedException(); case "y": case "Y": throw new NotImplementedException(); default: throw new NotImplementedException(); } throw new NotImplementedException(); } public string ToString(IFormatProvider formatProvider) { return ToString(); } public override string ToString() { return ToShortDateString() + " " + ToLongTimeString(); } #endregion #region Static Methods public static int Compare(GlobalDateTime t1, GlobalDateTime t2) { return t1.ticks.CompareTo(t2.ticks); } public static int DaysInMonth(int year, int month) { CheckMonth(month); switch(month) { case 1: return 29; case 13: if(IsLeapYear(year)) return 29; else return 28; default: return 28; } } public static int DaysInYear(int year) { if(IsLeapYear(year)) return (int)DaysPerLeapYear; else return (int)DaysPerYear; } public static bool Equals(GlobalDateTime dt1, GlobalDateTime dt2) { return dt1.ticks == dt2.ticks; } public static GlobalDateTime FromFileTime(long fileTime) { return DateTime.FromFileTime(fileTime); } public static GlobalDateTime FromFileTimeUtc(long fileTime) { return DateTime.FromFileTimeUtc(fileTime); } public static GlobalDateTime FromOADate(double d) { return DateTime.FromOADate(d); } public static bool IsLeapYear(int year) { return (year%4==0) && (year%100!=0 || year%400==0); } public static GlobalDateTime Parse(string s) { throw new NotImplementedException(); } public static GlobalDateTime Parse(string s, IFormatProvider provider) { throw new NotImplementedException(); } public static GlobalDateTime Parse(string s, IFormatProvider provider, DateTimeStyles style) { throw new NotImplementedException(); } public static GlobalDateTime ParseExact(string s, string format, IFormatProvider provider) { throw new NotImplementedException(); } public static GlobalDateTime ParseExact(string s, string format, IFormatProvider provider, DateTimeStyles style) { throw new NotImplementedException(); } public static GlobalDateTime ParseExact(string s, string[] formats, IFormatProvider provider, DateTimeStyles style) { throw new NotImplementedException(); } #endregion #region Operators public static GlobalDateTime operator +(GlobalDateTime d, TimeSpan t) { return new GlobalDateTime(d.Ticks + t.Ticks); } public static bool operator ==(GlobalDateTime d1, GlobalDateTime d2) { return d1.Ticks == d2.Ticks; } public static bool operator >(GlobalDateTime d1, GlobalDateTime d2) { return d1.Ticks > d2.Ticks; } public static bool operator >=(GlobalDateTime d1, GlobalDateTime d2) { return d1.Ticks >= d2.Ticks; } public static bool operator !=(GlobalDateTime d1, GlobalDateTime d2) { return d1.Ticks != d2.Ticks; } public static bool operator <(GlobalDateTime d1, GlobalDateTime d2) { return d1.Ticks < d2.Ticks; } public static bool operator <=(GlobalDateTime d1, GlobalDateTime d2) { return d1.Ticks <= d2.Ticks; } public static TimeSpan operator -(GlobalDateTime d1, GlobalDateTime d2) { return new TimeSpan(d1.Ticks - d2.Ticks); } public static GlobalDateTime operator -(GlobalDateTime d, TimeSpan t) { return new GlobalDateTime(d.Ticks - t.Ticks); } #endregion #region Implementation private static long YearToTicks(int year) { long yearsLeft = year; long cycle = Math.DivRem(yearsLeft, YearsPerCycle, out yearsLeft); long quarterCycle = Math.DivRem(yearsLeft, YearsPerQuarterCycle, out yearsLeft); long centiCycle = Math.DivRem(yearsLeft, YearsPerCentiCycle, out yearsLeft); long days = cycle * DaysPerCycle + quarterCycle * DaysPerQuarterCycle + centiCycle * DaysPerCentiCycle + yearsLeft * DaysPerYear; if(quarterCycle>1) days++; if(centiCycle>1) days--; if(yearsLeft>1) days++; return days * TimeSpan.TicksPerDay; } private static int TicksToYear(long ticks) { long daysLeft = ticks / TimeSpan.TicksPerDay; long cycle = Math.DivRem(daysLeft, DaysPerCycle, out daysLeft); //The first quarter cycle has an extra day in it daysLeft = (daysLeft==0) ? 0 : daysLeft-1; int quarterCycle = (int)Math.DivRem(daysLeft, DaysPerQuarterCycle, out daysLeft); //The first centi cycle is missing a day daysLeft = (daysLeft==0) ? 0 : daysLeft+1; int centiCycle = (int)Math.DivRem(daysLeft, DaysPerCentiCycle, out daysLeft); //The first year has an extra day daysLeft = (daysLeft==0) ? 0 : daysLeft-1; int yearsLeft = (int)(daysLeft / DaysPerYear); return (int)(cycle * YearsPerCycle + quarterCycle * YearsPerQuarterCycle + centiCycle * YearsPerCentiCycle + yearsLeft); } #endregion public static implicit operator GlobalDateTime(DateTime dt) { return new GlobalDateTime(dt.Ticks + TickDelta); } public static explicit operator DateTime(GlobalDateTime dt) { return new DateTime(dt.ticks - TickDelta); } #region IConvertible Members bool IConvertible.ToBoolean(IFormatProvider provider) { throw new InvalidCastException("Invalid cast from GlobalDateTime to Boolean."); } char IConvertible.ToChar(IFormatProvider provider) { throw new InvalidCastException("Invalid cast from GlobalDateTime to Char."); } DateTime IConvertible.ToDateTime(IFormatProvider provider) { return (DateTime)this; } decimal IConvertible.ToDecimal(IFormatProvider provider) { throw new InvalidCastException("Invalid cast from GlobalDateTime to Decimal."); } byte IConvertible.ToByte(IFormatProvider provider) { throw new InvalidCastException("Invalid cast from GlobalDateTime to Byte."); } sbyte IConvertible.ToSByte(IFormatProvider provider) { throw new InvalidCastException("Invalid cast from GlobalDateTime to SByte."); } short IConvertible.ToInt16(IFormatProvider provider) { throw new InvalidCastException("Invalid cast from GlobalDateTime to Int16."); } ushort IConvertible.ToUInt16(IFormatProvider provider) { throw new InvalidCastException("Invalid cast from GlobalDateTime to UInt16."); } int IConvertible.ToInt32(IFormatProvider provider) { throw new InvalidCastException("Invalid cast from GlobalDateTime to Int32."); } uint IConvertible.ToUInt32(IFormatProvider provider) { throw new InvalidCastException("Invalid cast from GlobalDateTime to UInt32."); } long IConvertible.ToInt64(IFormatProvider provider) { throw new InvalidCastException("Invalid cast from GlobalDateTime to Int64."); } ulong IConvertible.ToUInt64(IFormatProvider provider) { throw new InvalidCastException("Invalid cast from GlobalDateTime to UInt64."); } float IConvertible.ToSingle(IFormatProvider provider) { throw new InvalidCastException("Invalid cast from GlobalDateTime to Single."); } double IConvertible.ToDouble(IFormatProvider provider) { throw new InvalidCastException("Invalid cast from GlobalDateTime to Double."); } object IConvertible.ToType(Type conversionType, IFormatProvider provider) { if(conversionType == typeof(DateTime)) return ((IConvertible)this).ToDateTime(provider); else if(conversionType == typeof(string)) return ToString(provider); else throw new InvalidCastException("Invalid cast from GlobalDateTime to " + conversionType.FullName + "."); } #endregion #region Enforce Argument Ranges private static void CheckMonth(int month) { if(month<1 || month>13) throw new ArgumentOutOfRangeException("month", month, "Month must be between 1 and 13."); } private static void CheckDay(int year, int month, int day) { switch(month) { case 1: if(day<0 || day>28) throw new ArgumentOutOfRangeException("day", day, "Must be between 0 and 28 for January."); break; case 13: if(IsLeapYear(year)) { if(day<1 || day>29) throw new ArgumentOutOfRangeException("day", day, "Must be between 1 and 29 for December in leap years."); } else if(day<1 || day>28) throw new ArgumentOutOfRangeException("day", day, "Must be between 1 and 28 for December in non-leap years."); break; default: if(day<1 || day>28) throw new ArgumentOutOfRangeException("day", day, "Must be between 1 and 28 for normal months."); break; } } private static void CheckHour(int hour) { if(hour < 0 || hour > 23) throw new ArgumentOutOfRangeException("hour", hour, "Hour must be between 0 and 23."); } private static void CheckMinute(int minute) { if(minute < 0 || minute > 59) throw new ArgumentOutOfRangeException("minute", minute, "Minute must be between 0 and 59."); } private static void CheckSecond(int second) { if(second < 0 || second > 59) throw new ArgumentOutOfRangeException("second", second, "Second must be between 0 and 59."); } private static void CheckMillisecond(int millisecond) { if(millisecond < 0 || millisecond > 9999) throw new ArgumentOutOfRangeException("millisecond", millisecond, "Millisecond must be between 0 and 59."); } #endregion } }