DateTimeOffset.cs 43 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062
  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 ToLocalTime(DateTime.UtcNow, true);
  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 ToLocalTime(DateTime.FromFileTimeUtc(fileTime), true);
  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 ToLocalTime(UtcDateTime, throwOnOverflow);
  695. }
  696. private static DateTimeOffset ToLocalTime(DateTime utcDateTime, bool throwOnOverflow)
  697. {
  698. TimeSpan offset = TimeZoneInfo.GetLocalUtcOffset(utcDateTime, TimeZoneInfoOptions.NoThrowOnInvalidTime);
  699. long localTicks = utcDateTime.Ticks + offset.Ticks;
  700. if (localTicks < DateTime.MinTicks || localTicks > DateTime.MaxTicks)
  701. {
  702. if (throwOnOverflow)
  703. throw new ArgumentException(SR.Arg_ArgumentOutOfRangeException);
  704. localTicks = localTicks < DateTime.MinTicks ? DateTime.MinTicks : DateTime.MaxTicks;
  705. }
  706. return new DateTimeOffset(localTicks, offset);
  707. }
  708. public override string ToString()
  709. {
  710. return DateTimeFormat.Format(ClockDateTime, null, null, Offset);
  711. }
  712. public string ToString(string? format)
  713. {
  714. return DateTimeFormat.Format(ClockDateTime, format, null, Offset);
  715. }
  716. public string ToString(IFormatProvider? formatProvider)
  717. {
  718. return DateTimeFormat.Format(ClockDateTime, null, formatProvider, Offset);
  719. }
  720. public string ToString(string? format, IFormatProvider? formatProvider)
  721. {
  722. return DateTimeFormat.Format(ClockDateTime, format, formatProvider, Offset);
  723. }
  724. public bool TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format = default, IFormatProvider? formatProvider = null) =>
  725. DateTimeFormat.TryFormat(ClockDateTime, destination, out charsWritten, format, formatProvider, Offset);
  726. public DateTimeOffset ToUniversalTime()
  727. {
  728. return new DateTimeOffset(UtcDateTime);
  729. }
  730. public static bool TryParse(string? input, out DateTimeOffset result)
  731. {
  732. TimeSpan offset;
  733. DateTime dateResult;
  734. bool parsed = DateTimeParse.TryParse(input,
  735. DateTimeFormatInfo.CurrentInfo,
  736. DateTimeStyles.None,
  737. out dateResult,
  738. out offset);
  739. result = new DateTimeOffset(dateResult.Ticks, offset);
  740. return parsed;
  741. }
  742. public static bool TryParse(ReadOnlySpan<char> input, out DateTimeOffset result)
  743. {
  744. bool parsed = DateTimeParse.TryParse(input, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.None, out DateTime dateResult, out TimeSpan offset);
  745. result = new DateTimeOffset(dateResult.Ticks, offset);
  746. return parsed;
  747. }
  748. public static bool TryParse(string? input, IFormatProvider? formatProvider, DateTimeStyles styles, out DateTimeOffset result)
  749. {
  750. styles = ValidateStyles(styles, nameof(styles));
  751. if (input == null)
  752. {
  753. result = default;
  754. return false;
  755. }
  756. TimeSpan offset;
  757. DateTime dateResult;
  758. bool parsed = DateTimeParse.TryParse(input,
  759. DateTimeFormatInfo.GetInstance(formatProvider),
  760. styles,
  761. out dateResult,
  762. out offset);
  763. result = new DateTimeOffset(dateResult.Ticks, offset);
  764. return parsed;
  765. }
  766. public static bool TryParse(ReadOnlySpan<char> input, IFormatProvider? formatProvider, DateTimeStyles styles, out DateTimeOffset result)
  767. {
  768. styles = ValidateStyles(styles, nameof(styles));
  769. bool parsed = DateTimeParse.TryParse(input, DateTimeFormatInfo.GetInstance(formatProvider), styles, out DateTime dateResult, out TimeSpan offset);
  770. result = new DateTimeOffset(dateResult.Ticks, offset);
  771. return parsed;
  772. }
  773. public static bool TryParseExact(string? input, string? format, IFormatProvider? formatProvider, DateTimeStyles styles,
  774. out DateTimeOffset result)
  775. {
  776. styles = ValidateStyles(styles, nameof(styles));
  777. if (input == null || format == null)
  778. {
  779. result = default;
  780. return false;
  781. }
  782. TimeSpan offset;
  783. DateTime dateResult;
  784. bool parsed = DateTimeParse.TryParseExact(input,
  785. format,
  786. DateTimeFormatInfo.GetInstance(formatProvider),
  787. styles,
  788. out dateResult,
  789. out offset);
  790. result = new DateTimeOffset(dateResult.Ticks, offset);
  791. return parsed;
  792. }
  793. public static bool TryParseExact(
  794. ReadOnlySpan<char> input, ReadOnlySpan<char> format, IFormatProvider? formatProvider, DateTimeStyles styles, out DateTimeOffset result)
  795. {
  796. styles = ValidateStyles(styles, nameof(styles));
  797. bool parsed = DateTimeParse.TryParseExact(input, format, DateTimeFormatInfo.GetInstance(formatProvider), styles, out DateTime dateResult, out TimeSpan offset);
  798. result = new DateTimeOffset(dateResult.Ticks, offset);
  799. return parsed;
  800. }
  801. public static bool TryParseExact(string? input, string?[]? formats, IFormatProvider? formatProvider, DateTimeStyles styles,
  802. out DateTimeOffset result)
  803. {
  804. styles = ValidateStyles(styles, nameof(styles));
  805. if (input == null)
  806. {
  807. result = default;
  808. return false;
  809. }
  810. TimeSpan offset;
  811. DateTime dateResult;
  812. bool parsed = DateTimeParse.TryParseExactMultiple(input,
  813. formats,
  814. DateTimeFormatInfo.GetInstance(formatProvider),
  815. styles,
  816. out dateResult,
  817. out offset);
  818. result = new DateTimeOffset(dateResult.Ticks, offset);
  819. return parsed;
  820. }
  821. public static bool TryParseExact(
  822. ReadOnlySpan<char> input, string?[]? formats, IFormatProvider? formatProvider, DateTimeStyles styles, out DateTimeOffset result)
  823. {
  824. styles = ValidateStyles(styles, nameof(styles));
  825. bool parsed = DateTimeParse.TryParseExactMultiple(input, formats, DateTimeFormatInfo.GetInstance(formatProvider), styles, out DateTime dateResult, out TimeSpan offset);
  826. result = new DateTimeOffset(dateResult.Ticks, offset);
  827. return parsed;
  828. }
  829. // Ensures the TimeSpan is valid to go in a DateTimeOffset.
  830. private static short ValidateOffset(TimeSpan offset)
  831. {
  832. long ticks = offset.Ticks;
  833. if (ticks % TimeSpan.TicksPerMinute != 0)
  834. {
  835. throw new ArgumentException(SR.Argument_OffsetPrecision, nameof(offset));
  836. }
  837. if (ticks < MinOffset || ticks > MaxOffset)
  838. {
  839. throw new ArgumentOutOfRangeException(nameof(offset), SR.Argument_OffsetOutOfRange);
  840. }
  841. return (short)(offset.Ticks / TimeSpan.TicksPerMinute);
  842. }
  843. // Ensures that the time and offset are in range.
  844. private static DateTime ValidateDate(DateTime dateTime, TimeSpan offset)
  845. {
  846. // The key validation is that both the UTC and clock times fit. The clock time is validated
  847. // by the DateTime constructor.
  848. Debug.Assert(offset.Ticks >= MinOffset && offset.Ticks <= MaxOffset, "Offset not validated.");
  849. // This operation cannot overflow because offset should have already been validated to be within
  850. // 14 hours and the DateTime instance is more than that distance from the boundaries of long.
  851. long utcTicks = dateTime.Ticks - offset.Ticks;
  852. if (utcTicks < DateTime.MinTicks || utcTicks > DateTime.MaxTicks)
  853. {
  854. throw new ArgumentOutOfRangeException(nameof(offset), SR.Argument_UTCOutOfRange);
  855. }
  856. // make sure the Kind is set to Unspecified
  857. //
  858. return new DateTime(utcTicks, DateTimeKind.Unspecified);
  859. }
  860. private static DateTimeStyles ValidateStyles(DateTimeStyles style, string parameterName)
  861. {
  862. if ((style & DateTimeFormatInfo.InvalidDateTimeStyles) != 0)
  863. {
  864. throw new ArgumentException(SR.Argument_InvalidDateTimeStyles, parameterName);
  865. }
  866. if (((style & (DateTimeStyles.AssumeLocal)) != 0) && ((style & (DateTimeStyles.AssumeUniversal)) != 0))
  867. {
  868. throw new ArgumentException(SR.Argument_ConflictingDateTimeStyles, parameterName);
  869. }
  870. if ((style & DateTimeStyles.NoCurrentDateDefault) != 0)
  871. {
  872. throw new ArgumentException(SR.Argument_DateTimeOffsetInvalidDateTimeStyles, parameterName);
  873. }
  874. // RoundtripKind does not make sense for DateTimeOffset; ignore this flag for backward compatibility with DateTime
  875. style &= ~DateTimeStyles.RoundtripKind;
  876. // AssumeLocal is also ignored as that is what we do by default with DateTimeOffset.Parse
  877. style &= ~DateTimeStyles.AssumeLocal;
  878. return style;
  879. }
  880. // Operators
  881. public static implicit operator DateTimeOffset(DateTime dateTime)
  882. {
  883. return new DateTimeOffset(dateTime);
  884. }
  885. public static DateTimeOffset operator +(DateTimeOffset dateTimeOffset, TimeSpan timeSpan)
  886. {
  887. return new DateTimeOffset(dateTimeOffset.ClockDateTime + timeSpan, dateTimeOffset.Offset);
  888. }
  889. public static DateTimeOffset operator -(DateTimeOffset dateTimeOffset, TimeSpan timeSpan)
  890. {
  891. return new DateTimeOffset(dateTimeOffset.ClockDateTime - timeSpan, dateTimeOffset.Offset);
  892. }
  893. public static TimeSpan 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. public static bool operator <=(DateTimeOffset left, DateTimeOffset right)
  910. {
  911. return left.UtcDateTime <= right.UtcDateTime;
  912. }
  913. public static bool operator >(DateTimeOffset left, DateTimeOffset right)
  914. {
  915. return left.UtcDateTime > right.UtcDateTime;
  916. }
  917. public static bool operator >=(DateTimeOffset left, DateTimeOffset right)
  918. {
  919. return left.UtcDateTime >= right.UtcDateTime;
  920. }
  921. }
  922. }