DateTimeOffset.cs 42 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047
  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.
  4. using System.Diagnostics;
  5. using System.Globalization;
  6. using System.Runtime.InteropServices;
  7. using System.Runtime.Serialization;
  8. namespace System
  9. {
  10. // DateTimeOffset is a value type that consists of a DateTime and a time zone offset,
  11. // ie. how far away the time is from GMT. The DateTime is stored whole, and the offset
  12. // is stored as an Int16 internally to save space, but presented as a TimeSpan.
  13. //
  14. // The range is constrained so that both the represented clock time and the represented
  15. // UTC time fit within the boundaries of MaxValue. This gives it the same range as DateTime
  16. // for actual UTC times, and a slightly constrained range on one end when an offset is
  17. // present.
  18. //
  19. // This class should be substitutable for date time in most cases; so most operations
  20. // effectively work on the clock time. However, the underlying UTC time is what counts
  21. // for the purposes of identity, sorting and subtracting two instances.
  22. //
  23. //
  24. // There are theoretically two date times stored, the UTC and the relative local representation
  25. // or the 'clock' time. It actually does not matter which is stored in m_dateTime, so it is desirable
  26. // for most methods to go through the helpers UtcDateTime and ClockDateTime both to abstract this
  27. // out and for internal readability.
  28. [StructLayout(LayoutKind.Auto)]
  29. [Serializable]
  30. [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
  31. public readonly struct DateTimeOffset : IComparable, IFormattable, IComparable<DateTimeOffset>, IEquatable<DateTimeOffset>, ISerializable, IDeserializationCallback, ISpanFormattable
  32. {
  33. // Constants
  34. internal const long MaxOffset = TimeSpan.TicksPerHour * 14;
  35. internal const long MinOffset = -MaxOffset;
  36. private const long UnixEpochSeconds = DateTime.UnixEpochTicks / TimeSpan.TicksPerSecond; // 62,135,596,800
  37. private const long UnixEpochMilliseconds = DateTime.UnixEpochTicks / TimeSpan.TicksPerMillisecond; // 62,135,596,800,000
  38. internal const long UnixMinSeconds = DateTime.MinTicks / TimeSpan.TicksPerSecond - UnixEpochSeconds;
  39. internal const long UnixMaxSeconds = DateTime.MaxTicks / TimeSpan.TicksPerSecond - UnixEpochSeconds;
  40. // Static Fields
  41. public static readonly DateTimeOffset MinValue = new DateTimeOffset(DateTime.MinTicks, TimeSpan.Zero);
  42. public static readonly DateTimeOffset MaxValue = new DateTimeOffset(DateTime.MaxTicks, TimeSpan.Zero);
  43. public static readonly DateTimeOffset UnixEpoch = new DateTimeOffset(DateTime.UnixEpochTicks, TimeSpan.Zero);
  44. // Instance Fields
  45. private readonly DateTime _dateTime;
  46. private readonly short _offsetMinutes;
  47. // Constructors
  48. // Constructs a DateTimeOffset from a tick count and offset
  49. public DateTimeOffset(long ticks, TimeSpan offset)
  50. {
  51. _offsetMinutes = ValidateOffset(offset);
  52. // Let the DateTime constructor do the range checks
  53. DateTime dateTime = new DateTime(ticks);
  54. _dateTime = ValidateDate(dateTime, offset);
  55. }
  56. // Constructs a DateTimeOffset from a DateTime. For Local and Unspecified kinds,
  57. // extracts the local offset. For UTC, creates a UTC instance with a zero offset.
  58. public DateTimeOffset(DateTime dateTime)
  59. {
  60. TimeSpan offset;
  61. if (dateTime.Kind != DateTimeKind.Utc)
  62. {
  63. // Local and Unspecified are both treated as Local
  64. offset = TimeZoneInfo.GetLocalUtcOffset(dateTime, TimeZoneInfoOptions.NoThrowOnInvalidTime);
  65. }
  66. else
  67. {
  68. offset = new TimeSpan(0);
  69. }
  70. _offsetMinutes = ValidateOffset(offset);
  71. _dateTime = ValidateDate(dateTime, offset);
  72. }
  73. // Constructs a DateTimeOffset from a DateTime. And an offset. Always makes the clock time
  74. // consistent with the DateTime. For Utc ensures the offset is zero. For local, ensures that
  75. // the offset corresponds to the local.
  76. public DateTimeOffset(DateTime dateTime, TimeSpan offset)
  77. {
  78. if (dateTime.Kind == DateTimeKind.Local)
  79. {
  80. if (offset != TimeZoneInfo.GetLocalUtcOffset(dateTime, TimeZoneInfoOptions.NoThrowOnInvalidTime))
  81. {
  82. throw new ArgumentException(SR.Argument_OffsetLocalMismatch, nameof(offset));
  83. }
  84. }
  85. else if (dateTime.Kind == DateTimeKind.Utc)
  86. {
  87. if (offset != TimeSpan.Zero)
  88. {
  89. throw new ArgumentException(SR.Argument_OffsetUtcMismatch, nameof(offset));
  90. }
  91. }
  92. _offsetMinutes = ValidateOffset(offset);
  93. _dateTime = ValidateDate(dateTime, offset);
  94. }
  95. // Constructs a DateTimeOffset from a given year, month, day, hour,
  96. // minute, second and offset.
  97. public DateTimeOffset(int year, int month, int day, int hour, int minute, int second, TimeSpan offset)
  98. {
  99. _offsetMinutes = ValidateOffset(offset);
  100. int originalSecond = second;
  101. if (second == 60 && DateTime.s_systemSupportsLeapSeconds)
  102. {
  103. // Reset the leap second to 59 for now and then we'll validate it after getting the final UTC time.
  104. second = 59;
  105. }
  106. _dateTime = ValidateDate(new DateTime(year, month, day, hour, minute, second), offset);
  107. if (originalSecond == 60 &&
  108. !DateTime.IsValidTimeWithLeapSeconds(_dateTime.Year, _dateTime.Month, _dateTime.Day, _dateTime.Hour, _dateTime.Minute, 60, DateTimeKind.Utc))
  109. {
  110. throw new ArgumentOutOfRangeException(null, SR.ArgumentOutOfRange_BadHourMinuteSecond);
  111. }
  112. }
  113. // Constructs a DateTimeOffset from a given year, month, day, hour,
  114. // minute, second, millsecond and offset
  115. public DateTimeOffset(int year, int month, int day, int hour, int minute, int second, int millisecond, TimeSpan offset)
  116. {
  117. _offsetMinutes = ValidateOffset(offset);
  118. int originalSecond = second;
  119. if (second == 60 && DateTime.s_systemSupportsLeapSeconds)
  120. {
  121. // Reset the leap second to 59 for now and then we'll validate it after getting the final UTC time.
  122. second = 59;
  123. }
  124. _dateTime = ValidateDate(new DateTime(year, month, day, hour, minute, second, millisecond), offset);
  125. if (originalSecond == 60 &&
  126. !DateTime.IsValidTimeWithLeapSeconds(_dateTime.Year, _dateTime.Month, _dateTime.Day, _dateTime.Hour, _dateTime.Minute, 60, DateTimeKind.Utc))
  127. {
  128. throw new ArgumentOutOfRangeException(null, SR.ArgumentOutOfRange_BadHourMinuteSecond);
  129. }
  130. }
  131. // Constructs a DateTimeOffset from a given year, month, day, hour,
  132. // minute, second, millsecond, Calendar and offset.
  133. public DateTimeOffset(int year, int month, int day, int hour, int minute, int second, int millisecond, Calendar calendar, TimeSpan offset)
  134. {
  135. _offsetMinutes = ValidateOffset(offset);
  136. int originalSecond = second;
  137. if (second == 60 && DateTime.s_systemSupportsLeapSeconds)
  138. {
  139. // Reset the leap second to 59 for now and then we'll validate it after getting the final UTC time.
  140. second = 59;
  141. }
  142. _dateTime = ValidateDate(new DateTime(year, month, day, hour, minute, second, millisecond, calendar), offset);
  143. if (originalSecond == 60 &&
  144. !DateTime.IsValidTimeWithLeapSeconds(_dateTime.Year, _dateTime.Month, _dateTime.Day, _dateTime.Hour, _dateTime.Minute, 60, DateTimeKind.Utc))
  145. {
  146. throw new ArgumentOutOfRangeException(null, SR.ArgumentOutOfRange_BadHourMinuteSecond);
  147. }
  148. }
  149. // Returns a DateTimeOffset representing the current date and time. The
  150. // resolution of the returned value depends on the system timer.
  151. public static DateTimeOffset Now
  152. {
  153. get
  154. {
  155. return new DateTimeOffset(DateTime.Now);
  156. }
  157. }
  158. public static DateTimeOffset UtcNow
  159. {
  160. get
  161. {
  162. return new DateTimeOffset(DateTime.UtcNow);
  163. }
  164. }
  165. public DateTime DateTime
  166. {
  167. get
  168. {
  169. return ClockDateTime;
  170. }
  171. }
  172. public DateTime UtcDateTime
  173. {
  174. get
  175. {
  176. return DateTime.SpecifyKind(_dateTime, DateTimeKind.Utc);
  177. }
  178. }
  179. public DateTime LocalDateTime
  180. {
  181. get
  182. {
  183. return UtcDateTime.ToLocalTime();
  184. }
  185. }
  186. // Adjust to a given offset with the same UTC time. Can throw ArgumentException
  187. //
  188. public DateTimeOffset ToOffset(TimeSpan offset)
  189. {
  190. return new DateTimeOffset((_dateTime + offset).Ticks, offset);
  191. }
  192. // Instance Properties
  193. // The clock or visible time represented. This is just a wrapper around the internal date because this is
  194. // the chosen storage mechanism. Going through this helper is good for readability and maintainability.
  195. // This should be used for display but not identity.
  196. private DateTime ClockDateTime
  197. {
  198. get
  199. {
  200. return new DateTime((_dateTime + Offset).Ticks, DateTimeKind.Unspecified);
  201. }
  202. }
  203. // Returns the date part of this DateTimeOffset. The resulting value
  204. // corresponds to this DateTimeOffset with the time-of-day part set to
  205. // zero (midnight).
  206. //
  207. public DateTime Date
  208. {
  209. get
  210. {
  211. return ClockDateTime.Date;
  212. }
  213. }
  214. // Returns the day-of-month part of this DateTimeOffset. The returned
  215. // value is an integer between 1 and 31.
  216. //
  217. public int Day
  218. {
  219. get
  220. {
  221. return ClockDateTime.Day;
  222. }
  223. }
  224. // Returns the day-of-week part of this DateTimeOffset. The returned value
  225. // is an integer between 0 and 6, where 0 indicates Sunday, 1 indicates
  226. // Monday, 2 indicates Tuesday, 3 indicates Wednesday, 4 indicates
  227. // Thursday, 5 indicates Friday, and 6 indicates Saturday.
  228. //
  229. public DayOfWeek DayOfWeek
  230. {
  231. get
  232. {
  233. return ClockDateTime.DayOfWeek;
  234. }
  235. }
  236. // Returns the day-of-year part of this DateTimeOffset. The returned value
  237. // is an integer between 1 and 366.
  238. //
  239. public int DayOfYear
  240. {
  241. get
  242. {
  243. return ClockDateTime.DayOfYear;
  244. }
  245. }
  246. // Returns the hour part of this DateTimeOffset. The returned value is an
  247. // integer between 0 and 23.
  248. //
  249. public int Hour
  250. {
  251. get
  252. {
  253. return ClockDateTime.Hour;
  254. }
  255. }
  256. // Returns the millisecond part of this DateTimeOffset. The returned value
  257. // is an integer between 0 and 999.
  258. //
  259. public int Millisecond
  260. {
  261. get
  262. {
  263. return ClockDateTime.Millisecond;
  264. }
  265. }
  266. // Returns the minute part of this DateTimeOffset. The returned value is
  267. // an integer between 0 and 59.
  268. //
  269. public int Minute
  270. {
  271. get
  272. {
  273. return ClockDateTime.Minute;
  274. }
  275. }
  276. // Returns the month part of this DateTimeOffset. The returned value is an
  277. // integer between 1 and 12.
  278. //
  279. public int Month
  280. {
  281. get
  282. {
  283. return ClockDateTime.Month;
  284. }
  285. }
  286. public TimeSpan Offset
  287. {
  288. get
  289. {
  290. return new TimeSpan(0, _offsetMinutes, 0);
  291. }
  292. }
  293. // Returns the second part of this DateTimeOffset. The returned value is
  294. // an integer between 0 and 59.
  295. //
  296. public int Second
  297. {
  298. get
  299. {
  300. return ClockDateTime.Second;
  301. }
  302. }
  303. // Returns the tick count for this DateTimeOffset. The returned value is
  304. // the number of 100-nanosecond intervals that have elapsed since 1/1/0001
  305. // 12:00am.
  306. //
  307. public long Ticks
  308. {
  309. get
  310. {
  311. return ClockDateTime.Ticks;
  312. }
  313. }
  314. public long UtcTicks
  315. {
  316. get
  317. {
  318. return UtcDateTime.Ticks;
  319. }
  320. }
  321. // Returns the time-of-day part of this DateTimeOffset. The returned value
  322. // is a TimeSpan that indicates the time elapsed since midnight.
  323. //
  324. public TimeSpan TimeOfDay
  325. {
  326. get
  327. {
  328. return ClockDateTime.TimeOfDay;
  329. }
  330. }
  331. // Returns the year part of this DateTimeOffset. The returned value is an
  332. // integer between 1 and 9999.
  333. //
  334. public int Year
  335. {
  336. get
  337. {
  338. return ClockDateTime.Year;
  339. }
  340. }
  341. // Returns the DateTimeOffset resulting from adding the given
  342. // TimeSpan to this DateTimeOffset.
  343. //
  344. public DateTimeOffset Add(TimeSpan timeSpan)
  345. {
  346. return new DateTimeOffset(ClockDateTime.Add(timeSpan), Offset);
  347. }
  348. // Returns the DateTimeOffset resulting from adding a fractional number of
  349. // days to this DateTimeOffset. The result is computed by rounding the
  350. // fractional number of days given by value to the nearest
  351. // millisecond, and adding that interval to this DateTimeOffset. The
  352. // value argument is permitted to be negative.
  353. //
  354. public DateTimeOffset AddDays(double days)
  355. {
  356. return new DateTimeOffset(ClockDateTime.AddDays(days), Offset);
  357. }
  358. // Returns the DateTimeOffset resulting from adding a fractional number of
  359. // hours to this DateTimeOffset. The result is computed by rounding the
  360. // fractional number of hours given by value to the nearest
  361. // millisecond, and adding that interval to this DateTimeOffset. The
  362. // value argument is permitted to be negative.
  363. //
  364. public DateTimeOffset AddHours(double hours)
  365. {
  366. return new DateTimeOffset(ClockDateTime.AddHours(hours), Offset);
  367. }
  368. // Returns the DateTimeOffset resulting from the given number of
  369. // milliseconds to this DateTimeOffset. The result is computed by rounding
  370. // the number of milliseconds given by value to the nearest integer,
  371. // and adding that interval to this DateTimeOffset. The value
  372. // argument is permitted to be negative.
  373. //
  374. public DateTimeOffset AddMilliseconds(double milliseconds)
  375. {
  376. return new DateTimeOffset(ClockDateTime.AddMilliseconds(milliseconds), Offset);
  377. }
  378. // Returns the DateTimeOffset resulting from adding a fractional number of
  379. // minutes to this DateTimeOffset. The result is computed by rounding the
  380. // fractional number of minutes given by value to the nearest
  381. // millisecond, and adding that interval to this DateTimeOffset. The
  382. // value argument is permitted to be negative.
  383. //
  384. public DateTimeOffset AddMinutes(double minutes)
  385. {
  386. return new DateTimeOffset(ClockDateTime.AddMinutes(minutes), Offset);
  387. }
  388. public DateTimeOffset AddMonths(int months)
  389. {
  390. return new DateTimeOffset(ClockDateTime.AddMonths(months), Offset);
  391. }
  392. // Returns the DateTimeOffset resulting from adding a fractional number of
  393. // seconds to this DateTimeOffset. The result is computed by rounding the
  394. // fractional number of seconds given by value to the nearest
  395. // millisecond, and adding that interval to this DateTimeOffset. The
  396. // value argument is permitted to be negative.
  397. //
  398. public DateTimeOffset AddSeconds(double seconds)
  399. {
  400. return new DateTimeOffset(ClockDateTime.AddSeconds(seconds), Offset);
  401. }
  402. // Returns the DateTimeOffset resulting from adding the given number of
  403. // 100-nanosecond ticks to this DateTimeOffset. The value argument
  404. // is permitted to be negative.
  405. //
  406. public DateTimeOffset AddTicks(long ticks)
  407. {
  408. return new DateTimeOffset(ClockDateTime.AddTicks(ticks), Offset);
  409. }
  410. // Returns the DateTimeOffset resulting from adding the given number of
  411. // years to this DateTimeOffset. The result is computed by incrementing
  412. // (or decrementing) the year part of this DateTimeOffset by value
  413. // years. If the month and day of this DateTimeOffset is 2/29, and if the
  414. // resulting year is not a leap year, the month and day of the resulting
  415. // DateTimeOffset becomes 2/28. Otherwise, the month, day, and time-of-day
  416. // parts of the result are the same as those of this DateTimeOffset.
  417. //
  418. public DateTimeOffset AddYears(int years)
  419. {
  420. return new DateTimeOffset(ClockDateTime.AddYears(years), Offset);
  421. }
  422. // Compares two DateTimeOffset values, returning an integer that indicates
  423. // their relationship.
  424. //
  425. public static int Compare(DateTimeOffset first, DateTimeOffset second)
  426. {
  427. return DateTime.Compare(first.UtcDateTime, second.UtcDateTime);
  428. }
  429. // Compares this DateTimeOffset to a given object. This method provides an
  430. // implementation of the IComparable interface. The object
  431. // argument must be another DateTimeOffset, or otherwise an exception
  432. // occurs. Null is considered less than any instance.
  433. //
  434. int IComparable.CompareTo(object obj)
  435. {
  436. if (obj == null) return 1;
  437. if (!(obj is DateTimeOffset))
  438. {
  439. throw new ArgumentException(SR.Arg_MustBeDateTimeOffset);
  440. }
  441. DateTime objUtc = ((DateTimeOffset)obj).UtcDateTime;
  442. DateTime utc = UtcDateTime;
  443. if (utc > objUtc) return 1;
  444. if (utc < objUtc) return -1;
  445. return 0;
  446. }
  447. public int CompareTo(DateTimeOffset other)
  448. {
  449. DateTime otherUtc = other.UtcDateTime;
  450. DateTime utc = UtcDateTime;
  451. if (utc > otherUtc) return 1;
  452. if (utc < otherUtc) return -1;
  453. return 0;
  454. }
  455. // Checks if this DateTimeOffset is equal to a given object. Returns
  456. // true if the given object is a boxed DateTimeOffset and its value
  457. // is equal to the value of this DateTimeOffset. Returns false
  458. // otherwise.
  459. //
  460. public override bool Equals(object obj)
  461. {
  462. if (obj is DateTimeOffset)
  463. {
  464. return UtcDateTime.Equals(((DateTimeOffset)obj).UtcDateTime);
  465. }
  466. return false;
  467. }
  468. public bool Equals(DateTimeOffset other)
  469. {
  470. return UtcDateTime.Equals(other.UtcDateTime);
  471. }
  472. public bool EqualsExact(DateTimeOffset other)
  473. {
  474. //
  475. // returns true when the ClockDateTime, Kind, and Offset match
  476. //
  477. // currently the Kind should always be Unspecified, but there is always the possibility that a future version
  478. // of DateTimeOffset overloads the Kind field
  479. //
  480. return (ClockDateTime == other.ClockDateTime && Offset == other.Offset && ClockDateTime.Kind == other.ClockDateTime.Kind);
  481. }
  482. // Compares two DateTimeOffset values for equality. Returns true if
  483. // the two DateTimeOffset values are equal, or false if they are
  484. // not equal.
  485. //
  486. public static bool Equals(DateTimeOffset first, DateTimeOffset second)
  487. {
  488. return DateTime.Equals(first.UtcDateTime, second.UtcDateTime);
  489. }
  490. // Creates a DateTimeOffset from a Windows filetime. A Windows filetime is
  491. // a long representing the date and time as the number of
  492. // 100-nanosecond intervals that have elapsed since 1/1/1601 12:00am.
  493. //
  494. public static DateTimeOffset FromFileTime(long fileTime)
  495. {
  496. return new DateTimeOffset(DateTime.FromFileTime(fileTime));
  497. }
  498. public static DateTimeOffset FromUnixTimeSeconds(long seconds)
  499. {
  500. if (seconds < UnixMinSeconds || seconds > UnixMaxSeconds)
  501. {
  502. throw new ArgumentOutOfRangeException(nameof(seconds),
  503. SR.Format(SR.ArgumentOutOfRange_Range, UnixMinSeconds, UnixMaxSeconds));
  504. }
  505. long ticks = seconds * TimeSpan.TicksPerSecond + DateTime.UnixEpochTicks;
  506. return new DateTimeOffset(ticks, TimeSpan.Zero);
  507. }
  508. public static DateTimeOffset FromUnixTimeMilliseconds(long milliseconds)
  509. {
  510. const long MinMilliseconds = DateTime.MinTicks / TimeSpan.TicksPerMillisecond - UnixEpochMilliseconds;
  511. const long MaxMilliseconds = DateTime.MaxTicks / TimeSpan.TicksPerMillisecond - UnixEpochMilliseconds;
  512. if (milliseconds < MinMilliseconds || milliseconds > MaxMilliseconds)
  513. {
  514. throw new ArgumentOutOfRangeException(nameof(milliseconds),
  515. SR.Format(SR.ArgumentOutOfRange_Range, MinMilliseconds, MaxMilliseconds));
  516. }
  517. long ticks = milliseconds * TimeSpan.TicksPerMillisecond + DateTime.UnixEpochTicks;
  518. return new DateTimeOffset(ticks, TimeSpan.Zero);
  519. }
  520. // ----- SECTION: private serialization instance methods ----------------*
  521. void IDeserializationCallback.OnDeserialization(object sender)
  522. {
  523. try
  524. {
  525. ValidateOffset(Offset);
  526. ValidateDate(ClockDateTime, Offset);
  527. }
  528. catch (ArgumentException e)
  529. {
  530. throw new SerializationException(SR.Serialization_InvalidData, e);
  531. }
  532. }
  533. void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
  534. {
  535. if (info == null)
  536. {
  537. throw new ArgumentNullException(nameof(info));
  538. }
  539. info.AddValue("DateTime", _dateTime); // Do not rename (binary serialization)
  540. info.AddValue("OffsetMinutes", _offsetMinutes); // Do not rename (binary serialization)
  541. }
  542. private DateTimeOffset(SerializationInfo info, StreamingContext context)
  543. {
  544. if (info == null)
  545. {
  546. throw new ArgumentNullException(nameof(info));
  547. }
  548. _dateTime = (DateTime)info.GetValue("DateTime", typeof(DateTime)); // Do not rename (binary serialization)
  549. _offsetMinutes = (short)info.GetValue("OffsetMinutes", typeof(short)); // Do not rename (binary serialization)
  550. }
  551. // Returns the hash code for this DateTimeOffset.
  552. //
  553. public override int GetHashCode()
  554. {
  555. return UtcDateTime.GetHashCode();
  556. }
  557. // Constructs a DateTimeOffset from a string. The string must specify a
  558. // date and optionally a time in a culture-specific or universal format.
  559. // Leading and trailing whitespace characters are allowed.
  560. //
  561. public static DateTimeOffset Parse(string input)
  562. {
  563. if (input == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.input);
  564. TimeSpan offset;
  565. DateTime dateResult = DateTimeParse.Parse(input,
  566. DateTimeFormatInfo.CurrentInfo,
  567. DateTimeStyles.None,
  568. out offset);
  569. return new DateTimeOffset(dateResult.Ticks, offset);
  570. }
  571. // Constructs a DateTimeOffset from a string. The string must specify a
  572. // date and optionally a time in a culture-specific or universal format.
  573. // Leading and trailing whitespace characters are allowed.
  574. //
  575. public static DateTimeOffset Parse(string input, IFormatProvider formatProvider)
  576. {
  577. if (input == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.input);
  578. return Parse(input, formatProvider, DateTimeStyles.None);
  579. }
  580. public static DateTimeOffset Parse(string input, IFormatProvider formatProvider, DateTimeStyles styles)
  581. {
  582. styles = ValidateStyles(styles, nameof(styles));
  583. if (input == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.input);
  584. TimeSpan offset;
  585. DateTime dateResult = DateTimeParse.Parse(input,
  586. DateTimeFormatInfo.GetInstance(formatProvider),
  587. styles,
  588. out offset);
  589. return new DateTimeOffset(dateResult.Ticks, offset);
  590. }
  591. public static DateTimeOffset Parse(ReadOnlySpan<char> input, IFormatProvider formatProvider = null, DateTimeStyles styles = DateTimeStyles.None)
  592. {
  593. styles = ValidateStyles(styles, nameof(styles));
  594. DateTime dateResult = DateTimeParse.Parse(input, DateTimeFormatInfo.GetInstance(formatProvider), styles, out TimeSpan offset);
  595. return new DateTimeOffset(dateResult.Ticks, offset);
  596. }
  597. // Constructs a DateTimeOffset from a string. The string must specify a
  598. // date and optionally a time in a culture-specific or universal format.
  599. // Leading and trailing whitespace characters are allowed.
  600. //
  601. public static DateTimeOffset ParseExact(string input, string format, IFormatProvider formatProvider)
  602. {
  603. if (input == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.input);
  604. if (format == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.format);
  605. return ParseExact(input, format, formatProvider, DateTimeStyles.None);
  606. }
  607. // Constructs a DateTimeOffset from a string. The string must specify a
  608. // date and optionally a time in a culture-specific or universal format.
  609. // Leading and trailing whitespace characters are allowed.
  610. //
  611. public static DateTimeOffset ParseExact(string input, string format, IFormatProvider formatProvider, DateTimeStyles styles)
  612. {
  613. styles = ValidateStyles(styles, nameof(styles));
  614. if (input == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.input);
  615. if (format == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.format);
  616. TimeSpan offset;
  617. DateTime dateResult = DateTimeParse.ParseExact(input,
  618. format,
  619. DateTimeFormatInfo.GetInstance(formatProvider),
  620. styles,
  621. out offset);
  622. return new DateTimeOffset(dateResult.Ticks, offset);
  623. }
  624. public static DateTimeOffset ParseExact(ReadOnlySpan<char> input, ReadOnlySpan<char> format, IFormatProvider formatProvider, DateTimeStyles styles = DateTimeStyles.None)
  625. {
  626. styles = ValidateStyles(styles, nameof(styles));
  627. DateTime dateResult = DateTimeParse.ParseExact(input, format, DateTimeFormatInfo.GetInstance(formatProvider), styles, out TimeSpan offset);
  628. return new DateTimeOffset(dateResult.Ticks, offset);
  629. }
  630. public static DateTimeOffset ParseExact(string input, string[] formats, IFormatProvider formatProvider, DateTimeStyles styles)
  631. {
  632. styles = ValidateStyles(styles, nameof(styles));
  633. if (input == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.input);
  634. TimeSpan offset;
  635. DateTime dateResult = DateTimeParse.ParseExactMultiple(input,
  636. formats,
  637. DateTimeFormatInfo.GetInstance(formatProvider),
  638. styles,
  639. out offset);
  640. return new DateTimeOffset(dateResult.Ticks, offset);
  641. }
  642. public static DateTimeOffset ParseExact(ReadOnlySpan<char> input, string[] formats, IFormatProvider formatProvider, DateTimeStyles styles = DateTimeStyles.None)
  643. {
  644. styles = ValidateStyles(styles, nameof(styles));
  645. DateTime dateResult = DateTimeParse.ParseExactMultiple(input, formats, DateTimeFormatInfo.GetInstance(formatProvider), styles, out TimeSpan offset);
  646. return new DateTimeOffset(dateResult.Ticks, offset);
  647. }
  648. public TimeSpan Subtract(DateTimeOffset value)
  649. {
  650. return UtcDateTime.Subtract(value.UtcDateTime);
  651. }
  652. public DateTimeOffset Subtract(TimeSpan value)
  653. {
  654. return new DateTimeOffset(ClockDateTime.Subtract(value), Offset);
  655. }
  656. public long ToFileTime()
  657. {
  658. return UtcDateTime.ToFileTime();
  659. }
  660. public long ToUnixTimeSeconds()
  661. {
  662. // Truncate sub-second precision before offsetting by the Unix Epoch to avoid
  663. // the last digit being off by one for dates that result in negative Unix times.
  664. //
  665. // For example, consider the DateTimeOffset 12/31/1969 12:59:59.001 +0
  666. // ticks = 621355967990010000
  667. // ticksFromEpoch = ticks - DateTime.UnixEpochTicks = -9990000
  668. // secondsFromEpoch = ticksFromEpoch / TimeSpan.TicksPerSecond = 0
  669. //
  670. // Notice that secondsFromEpoch is rounded *up* by the truncation induced by integer division,
  671. // whereas we actually always want to round *down* when converting to Unix time. This happens
  672. // automatically for positive Unix time values. Now the example becomes:
  673. // seconds = ticks / TimeSpan.TicksPerSecond = 62135596799
  674. // secondsFromEpoch = seconds - UnixEpochSeconds = -1
  675. //
  676. // In other words, we want to consistently round toward the time 1/1/0001 00:00:00,
  677. // rather than toward the Unix Epoch (1/1/1970 00:00:00).
  678. long seconds = UtcDateTime.Ticks / TimeSpan.TicksPerSecond;
  679. return seconds - UnixEpochSeconds;
  680. }
  681. public long ToUnixTimeMilliseconds()
  682. {
  683. // Truncate sub-millisecond precision before offsetting by the Unix Epoch to avoid
  684. // the last digit being off by one for dates that result in negative Unix times
  685. long milliseconds = UtcDateTime.Ticks / TimeSpan.TicksPerMillisecond;
  686. return milliseconds - UnixEpochMilliseconds;
  687. }
  688. public DateTimeOffset ToLocalTime()
  689. {
  690. return ToLocalTime(false);
  691. }
  692. internal DateTimeOffset ToLocalTime(bool throwOnOverflow)
  693. {
  694. return new DateTimeOffset(UtcDateTime.ToLocalTime(throwOnOverflow));
  695. }
  696. public override string ToString()
  697. {
  698. return DateTimeFormat.Format(ClockDateTime, null, null, Offset);
  699. }
  700. public string ToString(string format)
  701. {
  702. return DateTimeFormat.Format(ClockDateTime, format, null, Offset);
  703. }
  704. public string ToString(IFormatProvider formatProvider)
  705. {
  706. return DateTimeFormat.Format(ClockDateTime, null, formatProvider, Offset);
  707. }
  708. public string ToString(string format, IFormatProvider formatProvider)
  709. {
  710. return DateTimeFormat.Format(ClockDateTime, format, formatProvider, Offset);
  711. }
  712. public bool TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format = default, IFormatProvider formatProvider = null) =>
  713. DateTimeFormat.TryFormat(ClockDateTime, destination, out charsWritten, format, formatProvider, Offset);
  714. public DateTimeOffset ToUniversalTime()
  715. {
  716. return new DateTimeOffset(UtcDateTime);
  717. }
  718. public static bool TryParse(string input, out DateTimeOffset result)
  719. {
  720. TimeSpan offset;
  721. DateTime dateResult;
  722. bool parsed = DateTimeParse.TryParse(input,
  723. DateTimeFormatInfo.CurrentInfo,
  724. DateTimeStyles.None,
  725. out dateResult,
  726. out offset);
  727. result = new DateTimeOffset(dateResult.Ticks, offset);
  728. return parsed;
  729. }
  730. public static bool TryParse(ReadOnlySpan<char> input, out DateTimeOffset result)
  731. {
  732. bool parsed = DateTimeParse.TryParse(input, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.None, out DateTime dateResult, out TimeSpan offset);
  733. result = new DateTimeOffset(dateResult.Ticks, offset);
  734. return parsed;
  735. }
  736. public static bool TryParse(string input, IFormatProvider formatProvider, DateTimeStyles styles, out DateTimeOffset result)
  737. {
  738. styles = ValidateStyles(styles, nameof(styles));
  739. if (input == null)
  740. {
  741. result = default;
  742. return false;
  743. }
  744. TimeSpan offset;
  745. DateTime dateResult;
  746. bool parsed = DateTimeParse.TryParse(input,
  747. DateTimeFormatInfo.GetInstance(formatProvider),
  748. styles,
  749. out dateResult,
  750. out offset);
  751. result = new DateTimeOffset(dateResult.Ticks, offset);
  752. return parsed;
  753. }
  754. public static bool TryParse(ReadOnlySpan<char> input, IFormatProvider formatProvider, DateTimeStyles styles, out DateTimeOffset result)
  755. {
  756. styles = ValidateStyles(styles, nameof(styles));
  757. bool parsed = DateTimeParse.TryParse(input, DateTimeFormatInfo.GetInstance(formatProvider), styles, out DateTime dateResult, out TimeSpan offset);
  758. result = new DateTimeOffset(dateResult.Ticks, offset);
  759. return parsed;
  760. }
  761. public static bool TryParseExact(string input, string format, IFormatProvider formatProvider, DateTimeStyles styles,
  762. out DateTimeOffset result)
  763. {
  764. styles = ValidateStyles(styles, nameof(styles));
  765. if (input == null || format == null)
  766. {
  767. result = default;
  768. return false;
  769. }
  770. TimeSpan offset;
  771. DateTime dateResult;
  772. bool parsed = DateTimeParse.TryParseExact(input,
  773. format,
  774. DateTimeFormatInfo.GetInstance(formatProvider),
  775. styles,
  776. out dateResult,
  777. out offset);
  778. result = new DateTimeOffset(dateResult.Ticks, offset);
  779. return parsed;
  780. }
  781. public static bool TryParseExact(
  782. ReadOnlySpan<char> input, ReadOnlySpan<char> format, IFormatProvider formatProvider, DateTimeStyles styles, out DateTimeOffset result)
  783. {
  784. styles = ValidateStyles(styles, nameof(styles));
  785. bool parsed = DateTimeParse.TryParseExact(input, format, DateTimeFormatInfo.GetInstance(formatProvider), styles, out DateTime dateResult, out TimeSpan offset);
  786. result = new DateTimeOffset(dateResult.Ticks, offset);
  787. return parsed;
  788. }
  789. public static bool TryParseExact(string input, string[] formats, IFormatProvider formatProvider, DateTimeStyles styles,
  790. out DateTimeOffset result)
  791. {
  792. styles = ValidateStyles(styles, nameof(styles));
  793. if (input == null)
  794. {
  795. result = default;
  796. return false;
  797. }
  798. TimeSpan offset;
  799. DateTime dateResult;
  800. bool parsed = DateTimeParse.TryParseExactMultiple(input,
  801. formats,
  802. DateTimeFormatInfo.GetInstance(formatProvider),
  803. styles,
  804. out dateResult,
  805. out offset);
  806. result = new DateTimeOffset(dateResult.Ticks, offset);
  807. return parsed;
  808. }
  809. public static bool TryParseExact(
  810. ReadOnlySpan<char> input, string[] formats, IFormatProvider formatProvider, DateTimeStyles styles, out DateTimeOffset result)
  811. {
  812. styles = ValidateStyles(styles, nameof(styles));
  813. bool parsed = DateTimeParse.TryParseExactMultiple(input, formats, DateTimeFormatInfo.GetInstance(formatProvider), styles, out DateTime dateResult, out TimeSpan offset);
  814. result = new DateTimeOffset(dateResult.Ticks, offset);
  815. return parsed;
  816. }
  817. // Ensures the TimeSpan is valid to go in a DateTimeOffset.
  818. private static short ValidateOffset(TimeSpan offset)
  819. {
  820. long ticks = offset.Ticks;
  821. if (ticks % TimeSpan.TicksPerMinute != 0)
  822. {
  823. throw new ArgumentException(SR.Argument_OffsetPrecision, nameof(offset));
  824. }
  825. if (ticks < MinOffset || ticks > MaxOffset)
  826. {
  827. throw new ArgumentOutOfRangeException(nameof(offset), SR.Argument_OffsetOutOfRange);
  828. }
  829. return (short)(offset.Ticks / TimeSpan.TicksPerMinute);
  830. }
  831. // Ensures that the time and offset are in range.
  832. private static DateTime ValidateDate(DateTime dateTime, TimeSpan offset)
  833. {
  834. // The key validation is that both the UTC and clock times fit. The clock time is validated
  835. // by the DateTime constructor.
  836. Debug.Assert(offset.Ticks >= MinOffset && offset.Ticks <= MaxOffset, "Offset not validated.");
  837. // This operation cannot overflow because offset should have already been validated to be within
  838. // 14 hours and the DateTime instance is more than that distance from the boundaries of long.
  839. long utcTicks = dateTime.Ticks - offset.Ticks;
  840. if (utcTicks < DateTime.MinTicks || utcTicks > DateTime.MaxTicks)
  841. {
  842. throw new ArgumentOutOfRangeException(nameof(offset), SR.Argument_UTCOutOfRange);
  843. }
  844. // make sure the Kind is set to Unspecified
  845. //
  846. return new DateTime(utcTicks, DateTimeKind.Unspecified);
  847. }
  848. private static DateTimeStyles ValidateStyles(DateTimeStyles style, string parameterName)
  849. {
  850. if ((style & DateTimeFormatInfo.InvalidDateTimeStyles) != 0)
  851. {
  852. throw new ArgumentException(SR.Argument_InvalidDateTimeStyles, parameterName);
  853. }
  854. if (((style & (DateTimeStyles.AssumeLocal)) != 0) && ((style & (DateTimeStyles.AssumeUniversal)) != 0))
  855. {
  856. throw new ArgumentException(SR.Argument_ConflictingDateTimeStyles, parameterName);
  857. }
  858. if ((style & DateTimeStyles.NoCurrentDateDefault) != 0)
  859. {
  860. throw new ArgumentException(SR.Argument_DateTimeOffsetInvalidDateTimeStyles, parameterName);
  861. }
  862. // RoundtripKind does not make sense for DateTimeOffset; ignore this flag for backward compatibility with DateTime
  863. style &= ~DateTimeStyles.RoundtripKind;
  864. // AssumeLocal is also ignored as that is what we do by default with DateTimeOffset.Parse
  865. style &= ~DateTimeStyles.AssumeLocal;
  866. return style;
  867. }
  868. // Operators
  869. public static implicit operator DateTimeOffset(DateTime dateTime)
  870. {
  871. return new DateTimeOffset(dateTime);
  872. }
  873. public static DateTimeOffset operator +(DateTimeOffset dateTimeOffset, TimeSpan timeSpan)
  874. {
  875. return new DateTimeOffset(dateTimeOffset.ClockDateTime + timeSpan, dateTimeOffset.Offset);
  876. }
  877. public static DateTimeOffset operator -(DateTimeOffset dateTimeOffset, TimeSpan timeSpan)
  878. {
  879. return new DateTimeOffset(dateTimeOffset.ClockDateTime - timeSpan, dateTimeOffset.Offset);
  880. }
  881. public static TimeSpan operator -(DateTimeOffset left, DateTimeOffset right)
  882. {
  883. return left.UtcDateTime - right.UtcDateTime;
  884. }
  885. public static bool operator ==(DateTimeOffset left, DateTimeOffset right)
  886. {
  887. return left.UtcDateTime == right.UtcDateTime;
  888. }
  889. public static bool operator !=(DateTimeOffset left, DateTimeOffset right)
  890. {
  891. return left.UtcDateTime != right.UtcDateTime;
  892. }
  893. public static bool operator <(DateTimeOffset left, DateTimeOffset right)
  894. {
  895. return left.UtcDateTime < right.UtcDateTime;
  896. }
  897. public static bool operator <=(DateTimeOffset left, DateTimeOffset right)
  898. {
  899. return left.UtcDateTime <= right.UtcDateTime;
  900. }
  901. public static bool operator >(DateTimeOffset left, DateTimeOffset right)
  902. {
  903. return left.UtcDateTime > right.UtcDateTime;
  904. }
  905. public static bool operator >=(DateTimeOffset left, DateTimeOffset right)
  906. {
  907. return left.UtcDateTime >= right.UtcDateTime;
  908. }
  909. }
  910. }