|
| 1 | +using System; |
| 2 | +using System.Collections.Generic; |
| 3 | +using System.Text; |
| 4 | + |
| 5 | +namespace RL.FileGenerators.Excel |
| 6 | +{ |
| 7 | + /* |
| 8 | + * the DateTime type doesn't have "ToOADate" method in .NET Standard 1.3, so I have to copy |
| 9 | + * its code from coreclr repository https://github.com/dotnet/coreclr/blob/release/1.0.0/src/mscorlib/src/System/DateTime.cs |
| 10 | + * and added this extention method. |
| 11 | + */ |
| 12 | + public static class DateTimeExtension |
| 13 | + { |
| 14 | + // Number of 100ns ticks per time unit |
| 15 | + private const long TicksPerMillisecond = 10000; |
| 16 | + private const long TicksPerSecond = TicksPerMillisecond * 1000; |
| 17 | + private const long TicksPerMinute = TicksPerSecond * 60; |
| 18 | + private const long TicksPerHour = TicksPerMinute * 60; |
| 19 | + private const long TicksPerDay = TicksPerHour * 24; |
| 20 | + |
| 21 | + // Number of milliseconds per time unit |
| 22 | + private const int MillisPerSecond = 1000; |
| 23 | + private const int MillisPerMinute = MillisPerSecond * 60; |
| 24 | + private const int MillisPerHour = MillisPerMinute * 60; |
| 25 | + private const int MillisPerDay = MillisPerHour * 24; |
| 26 | + |
| 27 | + // Number of days in a non-leap year |
| 28 | + private const int DaysPerYear = 365; |
| 29 | + // Number of days in 4 years |
| 30 | + private const int DaysPer4Years = DaysPerYear * 4 + 1; // 1461 |
| 31 | + // Number of days in 100 years |
| 32 | + private const int DaysPer100Years = DaysPer4Years * 25 - 1; // 36524 |
| 33 | + // Number of days in 400 years |
| 34 | + private const int DaysPer400Years = DaysPer100Years * 4 + 1; // 146097 |
| 35 | + |
| 36 | + // Number of days from 1/1/0001 to 12/31/1600 |
| 37 | + private const int DaysTo1601 = DaysPer400Years * 4; // 584388 |
| 38 | + // Number of days from 1/1/0001 to 12/30/1899 |
| 39 | + private const int DaysTo1899 = DaysPer400Years * 4 + DaysPer100Years * 3 - 367; |
| 40 | + // Number of days from 1/1/0001 to 12/31/1969 |
| 41 | + internal const int DaysTo1970 = DaysPer400Years * 4 + DaysPer100Years * 3 + DaysPer4Years * 17 + DaysPerYear; // 719,162 |
| 42 | + // Number of days from 1/1/0001 to 12/31/9999 |
| 43 | + private const int DaysTo10000 = DaysPer400Years * 25 - 366; // 3652059 |
| 44 | + |
| 45 | + internal const long MinTicks = 0; |
| 46 | + internal const long MaxTicks = DaysTo10000 * TicksPerDay - 1; |
| 47 | + private const long MaxMillis = (long)DaysTo10000 * MillisPerDay; |
| 48 | + |
| 49 | + private const long FileTimeOffset = DaysTo1601 * TicksPerDay; |
| 50 | + private const long DoubleDateOffset = DaysTo1899 * TicksPerDay; |
| 51 | + // The minimum OA date is 0100/01/01 (Note it's year 100). |
| 52 | + // The maximum OA date is 9999/12/31 |
| 53 | + private const long OADateMinAsTicks = (DaysPer100Years - DaysPerYear) * TicksPerDay; |
| 54 | + // All OA dates must be greater than (not >=) OADateMinAsDouble |
| 55 | + private const double OADateMinAsDouble = -657435.0; |
| 56 | + // All OA dates must be less than (not <=) OADateMaxAsDouble |
| 57 | + private const double OADateMaxAsDouble = 2958466.0; |
| 58 | + |
| 59 | + private const int DatePartYear = 0; |
| 60 | + private const int DatePartDayOfYear = 1; |
| 61 | + private const int DatePartMonth = 2; |
| 62 | + private const int DatePartDay = 3; |
| 63 | + |
| 64 | + private static readonly int[] DaysToMonth365 = { |
| 65 | + 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365}; |
| 66 | + private static readonly int[] DaysToMonth366 = { |
| 67 | + 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366}; |
| 68 | + |
| 69 | + public static readonly DateTime MinValue = new DateTime(MinTicks, DateTimeKind.Unspecified); |
| 70 | + public static readonly DateTime MaxValue = new DateTime(MaxTicks, DateTimeKind.Unspecified); |
| 71 | + |
| 72 | + private const UInt64 TicksMask = 0x3FFFFFFFFFFFFFFF; |
| 73 | + private const UInt64 FlagsMask = 0xC000000000000000; |
| 74 | + private const UInt64 LocalMask = 0x8000000000000000; |
| 75 | + private const Int64 TicksCeiling = 0x4000000000000000; |
| 76 | + private const UInt64 KindUnspecified = 0x0000000000000000; |
| 77 | + private const UInt64 KindUtc = 0x4000000000000000; |
| 78 | + private const UInt64 KindLocal = 0x8000000000000000; |
| 79 | + private const UInt64 KindLocalAmbiguousDst = 0xC000000000000000; |
| 80 | + |
| 81 | + public static double ToOADate(this DateTime dt) |
| 82 | + { |
| 83 | + long value = dt.Ticks; |
| 84 | + |
| 85 | + if (value == 0) |
| 86 | + return 0.0; // Returns OleAut's zero'ed date value. |
| 87 | + |
| 88 | + if (value < TicksPerDay) // This is a fix for VB. They want the default day to be 1/1/0001 rathar then 12/30/1899. |
| 89 | + value += DoubleDateOffset; // We could have moved this fix down but we would like to keep the bounds check. |
| 90 | + |
| 91 | + if (value < OADateMinAsTicks) |
| 92 | + throw new OverflowException("Arg_OleAutDateInvalid"); |
| 93 | + |
| 94 | + // Currently, our max date == OA's max date (12/31/9999), so we don't |
| 95 | + // need an overflow check in that direction. |
| 96 | + long millis = (value - DoubleDateOffset) / TicksPerMillisecond; |
| 97 | + if (millis < 0) |
| 98 | + { |
| 99 | + long frac = millis % MillisPerDay; |
| 100 | + if (frac != 0) millis -= (MillisPerDay + frac) * 2; |
| 101 | + } |
| 102 | + |
| 103 | + return (double)millis / MillisPerDay; |
| 104 | + } |
| 105 | + } |
| 106 | +} |
0 commit comments