DateTimeOffset.cs 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855
  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 => ToLocalTime(DateTime.UtcNow, true);
  152. public static DateTimeOffset UtcNow => new DateTimeOffset(DateTime.UtcNow);
  153. public DateTime DateTime => ClockDateTime;
  154. public DateTime UtcDateTime => DateTime.SpecifyKind(_dateTime, DateTimeKind.Utc);
  155. public DateTime LocalDateTime => UtcDateTime.ToLocalTime();
  156. // Adjust to a given offset with the same UTC time. Can throw ArgumentException
  157. //
  158. public DateTimeOffset ToOffset(TimeSpan offset) =>
  159. new DateTimeOffset((_dateTime + offset).Ticks, offset);
  160. // Instance Properties
  161. // The clock or visible time represented. This is just a wrapper around the internal date because this is
  162. // the chosen storage mechanism. Going through this helper is good for readability and maintainability.
  163. // This should be used for display but not identity.
  164. private DateTime ClockDateTime => new DateTime((_dateTime + Offset).Ticks, DateTimeKind.Unspecified);
  165. // Returns the date part of this DateTimeOffset. The resulting value
  166. // corresponds to this DateTimeOffset with the time-of-day part set to
  167. // zero (midnight).
  168. //
  169. public DateTime Date => ClockDateTime.Date;
  170. // Returns the day-of-month part of this DateTimeOffset. The returned
  171. // value is an integer between 1 and 31.
  172. //
  173. public int Day => ClockDateTime.Day;
  174. // Returns the day-of-week part of this DateTimeOffset. The returned value
  175. // is an integer between 0 and 6, where 0 indicates Sunday, 1 indicates
  176. // Monday, 2 indicates Tuesday, 3 indicates Wednesday, 4 indicates
  177. // Thursday, 5 indicates Friday, and 6 indicates Saturday.
  178. //
  179. public DayOfWeek DayOfWeek => ClockDateTime.DayOfWeek;
  180. // Returns the day-of-year part of this DateTimeOffset. The returned value
  181. // is an integer between 1 and 366.
  182. //
  183. public int DayOfYear => ClockDateTime.DayOfYear;
  184. // Returns the hour part of this DateTimeOffset. The returned value is an
  185. // integer between 0 and 23.
  186. //
  187. public int Hour => ClockDateTime.Hour;
  188. // Returns the millisecond part of this DateTimeOffset. The returned value
  189. // is an integer between 0 and 999.
  190. //
  191. public int Millisecond => ClockDateTime.Millisecond;
  192. // Returns the minute part of this DateTimeOffset. The returned value is
  193. // an integer between 0 and 59.
  194. //
  195. public int Minute => ClockDateTime.Minute;
  196. // Returns the month part of this DateTimeOffset. The returned value is an
  197. // integer between 1 and 12.
  198. //
  199. public int Month => ClockDateTime.Month;
  200. public TimeSpan Offset => new TimeSpan(0, _offsetMinutes, 0);
  201. // Returns the second part of this DateTimeOffset. The returned value is
  202. // an integer between 0 and 59.
  203. //
  204. public int Second => ClockDateTime.Second;
  205. // Returns the tick count for this DateTimeOffset. The returned value is
  206. // the number of 100-nanosecond intervals that have elapsed since 1/1/0001
  207. // 12:00am.
  208. //
  209. public long Ticks => ClockDateTime.Ticks;
  210. public long UtcTicks => UtcDateTime.Ticks;
  211. // Returns the time-of-day part of this DateTimeOffset. The returned value
  212. // is a TimeSpan that indicates the time elapsed since midnight.
  213. //
  214. public TimeSpan TimeOfDay => ClockDateTime.TimeOfDay;
  215. // Returns the year part of this DateTimeOffset. The returned value is an
  216. // integer between 1 and 9999.
  217. //
  218. public int Year => ClockDateTime.Year;
  219. // Returns the DateTimeOffset resulting from adding the given
  220. // TimeSpan to this DateTimeOffset.
  221. //
  222. public DateTimeOffset Add(TimeSpan timeSpan) =>
  223. new DateTimeOffset(ClockDateTime.Add(timeSpan), Offset);
  224. // Returns the DateTimeOffset resulting from adding a fractional number of
  225. // days to this DateTimeOffset. The result is computed by rounding the
  226. // fractional number of days given by value to the nearest
  227. // millisecond, and adding that interval to this DateTimeOffset. The
  228. // value argument is permitted to be negative.
  229. //
  230. public DateTimeOffset AddDays(double days) =>
  231. new DateTimeOffset(ClockDateTime.AddDays(days), Offset);
  232. // Returns the DateTimeOffset resulting from adding a fractional number of
  233. // hours to this DateTimeOffset. The result is computed by rounding the
  234. // fractional number of hours given by value to the nearest
  235. // millisecond, and adding that interval to this DateTimeOffset. The
  236. // value argument is permitted to be negative.
  237. //
  238. public DateTimeOffset AddHours(double hours) =>
  239. new DateTimeOffset(ClockDateTime.AddHours(hours), Offset);
  240. // Returns the DateTimeOffset resulting from the given number of
  241. // milliseconds to this DateTimeOffset. The result is computed by rounding
  242. // the number of milliseconds given by value to the nearest integer,
  243. // and adding that interval to this DateTimeOffset. The value
  244. // argument is permitted to be negative.
  245. //
  246. public DateTimeOffset AddMilliseconds(double milliseconds) =>
  247. new DateTimeOffset(ClockDateTime.AddMilliseconds(milliseconds), Offset);
  248. // Returns the DateTimeOffset resulting from adding a fractional number of
  249. // minutes to this DateTimeOffset. The result is computed by rounding the
  250. // fractional number of minutes given by value to the nearest
  251. // millisecond, and adding that interval to this DateTimeOffset. The
  252. // value argument is permitted to be negative.
  253. //
  254. public DateTimeOffset AddMinutes(double minutes) =>
  255. new DateTimeOffset(ClockDateTime.AddMinutes(minutes), Offset);
  256. public DateTimeOffset AddMonths(int months) =>
  257. new DateTimeOffset(ClockDateTime.AddMonths(months), Offset);
  258. // Returns the DateTimeOffset resulting from adding a fractional number of
  259. // seconds to this DateTimeOffset. The result is computed by rounding the
  260. // fractional number of seconds given by value to the nearest
  261. // millisecond, and adding that interval to this DateTimeOffset. The
  262. // value argument is permitted to be negative.
  263. //
  264. public DateTimeOffset AddSeconds(double seconds) =>
  265. new DateTimeOffset(ClockDateTime.AddSeconds(seconds), Offset);
  266. // Returns the DateTimeOffset resulting from adding the given number of
  267. // 100-nanosecond ticks to this DateTimeOffset. The value argument
  268. // is permitted to be negative.
  269. //
  270. public DateTimeOffset AddTicks(long ticks) =>
  271. new DateTimeOffset(ClockDateTime.AddTicks(ticks), Offset);
  272. // Returns the DateTimeOffset resulting from adding the given number of
  273. // years to this DateTimeOffset. The result is computed by incrementing
  274. // (or decrementing) the year part of this DateTimeOffset by value
  275. // years. If the month and day of this DateTimeOffset is 2/29, and if the
  276. // resulting year is not a leap year, the month and day of the resulting
  277. // DateTimeOffset becomes 2/28. Otherwise, the month, day, and time-of-day
  278. // parts of the result are the same as those of this DateTimeOffset.
  279. //
  280. public DateTimeOffset AddYears(int years) =>
  281. new DateTimeOffset(ClockDateTime.AddYears(years), Offset);
  282. // Compares two DateTimeOffset values, returning an integer that indicates
  283. // their relationship.
  284. //
  285. public static int Compare(DateTimeOffset first, DateTimeOffset second) =>
  286. DateTime.Compare(first.UtcDateTime, second.UtcDateTime);
  287. // Compares this DateTimeOffset to a given object. This method provides an
  288. // implementation of the IComparable interface. The object
  289. // argument must be another DateTimeOffset, or otherwise an exception
  290. // occurs. Null is considered less than any instance.
  291. //
  292. int IComparable.CompareTo(object? obj)
  293. {
  294. if (obj == null) return 1;
  295. if (!(obj is DateTimeOffset))
  296. {
  297. throw new ArgumentException(SR.Arg_MustBeDateTimeOffset);
  298. }
  299. DateTime objUtc = ((DateTimeOffset)obj).UtcDateTime;
  300. DateTime utc = UtcDateTime;
  301. if (utc > objUtc) return 1;
  302. if (utc < objUtc) return -1;
  303. return 0;
  304. }
  305. public int CompareTo(DateTimeOffset other)
  306. {
  307. DateTime otherUtc = other.UtcDateTime;
  308. DateTime utc = UtcDateTime;
  309. if (utc > otherUtc) return 1;
  310. if (utc < otherUtc) return -1;
  311. return 0;
  312. }
  313. // Checks if this DateTimeOffset is equal to a given object. Returns
  314. // true if the given object is a boxed DateTimeOffset and its value
  315. // is equal to the value of this DateTimeOffset. Returns false
  316. // otherwise.
  317. //
  318. public override bool Equals(object? obj) =>
  319. obj is DateTimeOffset && UtcDateTime.Equals(((DateTimeOffset)obj).UtcDateTime);
  320. public bool Equals(DateTimeOffset other) =>
  321. UtcDateTime.Equals(other.UtcDateTime);
  322. public bool EqualsExact(DateTimeOffset other) =>
  323. //
  324. // returns true when the ClockDateTime, Kind, and Offset match
  325. //
  326. // currently the Kind should always be Unspecified, but there is always the possibility that a future version
  327. // of DateTimeOffset overloads the Kind field
  328. //
  329. ClockDateTime == other.ClockDateTime && Offset == other.Offset && ClockDateTime.Kind == other.ClockDateTime.Kind;
  330. // Compares two DateTimeOffset values for equality. Returns true if
  331. // the two DateTimeOffset values are equal, or false if they are
  332. // not equal.
  333. //
  334. public static bool Equals(DateTimeOffset first, DateTimeOffset second) =>
  335. DateTime.Equals(first.UtcDateTime, second.UtcDateTime);
  336. // Creates a DateTimeOffset from a Windows filetime. A Windows filetime is
  337. // a long representing the date and time as the number of
  338. // 100-nanosecond intervals that have elapsed since 1/1/1601 12:00am.
  339. //
  340. public static DateTimeOffset FromFileTime(long fileTime) =>
  341. ToLocalTime(DateTime.FromFileTimeUtc(fileTime), true);
  342. public static DateTimeOffset FromUnixTimeSeconds(long seconds)
  343. {
  344. if (seconds < UnixMinSeconds || seconds > UnixMaxSeconds)
  345. {
  346. throw new ArgumentOutOfRangeException(nameof(seconds),
  347. SR.Format(SR.ArgumentOutOfRange_Range, UnixMinSeconds, UnixMaxSeconds));
  348. }
  349. long ticks = seconds * TimeSpan.TicksPerSecond + DateTime.UnixEpochTicks;
  350. return new DateTimeOffset(ticks, TimeSpan.Zero);
  351. }
  352. public static DateTimeOffset FromUnixTimeMilliseconds(long milliseconds)
  353. {
  354. const long MinMilliseconds = DateTime.MinTicks / TimeSpan.TicksPerMillisecond - UnixEpochMilliseconds;
  355. const long MaxMilliseconds = DateTime.MaxTicks / TimeSpan.TicksPerMillisecond - UnixEpochMilliseconds;
  356. if (milliseconds < MinMilliseconds || milliseconds > MaxMilliseconds)
  357. {
  358. throw new ArgumentOutOfRangeException(nameof(milliseconds),
  359. SR.Format(SR.ArgumentOutOfRange_Range, MinMilliseconds, MaxMilliseconds));
  360. }
  361. long ticks = milliseconds * TimeSpan.TicksPerMillisecond + DateTime.UnixEpochTicks;
  362. return new DateTimeOffset(ticks, TimeSpan.Zero);
  363. }
  364. // ----- SECTION: private serialization instance methods ----------------*
  365. void IDeserializationCallback.OnDeserialization(object? sender)
  366. {
  367. try
  368. {
  369. ValidateOffset(Offset);
  370. ValidateDate(ClockDateTime, Offset);
  371. }
  372. catch (ArgumentException e)
  373. {
  374. throw new SerializationException(SR.Serialization_InvalidData, e);
  375. }
  376. }
  377. void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
  378. {
  379. if (info == null)
  380. {
  381. throw new ArgumentNullException(nameof(info));
  382. }
  383. info.AddValue("DateTime", _dateTime); // Do not rename (binary serialization)
  384. info.AddValue("OffsetMinutes", _offsetMinutes); // Do not rename (binary serialization)
  385. }
  386. private DateTimeOffset(SerializationInfo info, StreamingContext context)
  387. {
  388. if (info == null)
  389. {
  390. throw new ArgumentNullException(nameof(info));
  391. }
  392. _dateTime = (DateTime)info.GetValue("DateTime", typeof(DateTime))!; // Do not rename (binary serialization)
  393. _offsetMinutes = (short)info.GetValue("OffsetMinutes", typeof(short))!; // Do not rename (binary serialization)
  394. }
  395. // Returns the hash code for this DateTimeOffset.
  396. //
  397. public override int GetHashCode() => UtcDateTime.GetHashCode();
  398. // Constructs a DateTimeOffset from a string. The string must specify a
  399. // date and optionally a time in a culture-specific or universal format.
  400. // Leading and trailing whitespace characters are allowed.
  401. //
  402. public static DateTimeOffset Parse(string input)
  403. {
  404. if (input == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.input);
  405. TimeSpan offset;
  406. DateTime dateResult = DateTimeParse.Parse(input,
  407. DateTimeFormatInfo.CurrentInfo,
  408. DateTimeStyles.None,
  409. out offset);
  410. return new DateTimeOffset(dateResult.Ticks, offset);
  411. }
  412. // Constructs a DateTimeOffset from a string. The string must specify a
  413. // date and optionally a time in a culture-specific or universal format.
  414. // Leading and trailing whitespace characters are allowed.
  415. //
  416. public static DateTimeOffset Parse(string input, IFormatProvider? formatProvider)
  417. {
  418. if (input == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.input);
  419. return Parse(input, formatProvider, DateTimeStyles.None);
  420. }
  421. public static DateTimeOffset Parse(string input, IFormatProvider? formatProvider, DateTimeStyles styles)
  422. {
  423. styles = ValidateStyles(styles, nameof(styles));
  424. if (input == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.input);
  425. TimeSpan offset;
  426. DateTime dateResult = DateTimeParse.Parse(input,
  427. DateTimeFormatInfo.GetInstance(formatProvider),
  428. styles,
  429. out offset);
  430. return new DateTimeOffset(dateResult.Ticks, offset);
  431. }
  432. public static DateTimeOffset Parse(ReadOnlySpan<char> input, IFormatProvider? formatProvider = null, DateTimeStyles styles = DateTimeStyles.None)
  433. {
  434. styles = ValidateStyles(styles, nameof(styles));
  435. DateTime dateResult = DateTimeParse.Parse(input, DateTimeFormatInfo.GetInstance(formatProvider), styles, out TimeSpan offset);
  436. return new DateTimeOffset(dateResult.Ticks, offset);
  437. }
  438. // Constructs a DateTimeOffset from a string. The string must specify a
  439. // date and optionally a time in a culture-specific or universal format.
  440. // Leading and trailing whitespace characters are allowed.
  441. //
  442. public static DateTimeOffset ParseExact(string input, string format, IFormatProvider? formatProvider)
  443. {
  444. if (input == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.input);
  445. if (format == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.format);
  446. return ParseExact(input, format, formatProvider, DateTimeStyles.None);
  447. }
  448. // Constructs a DateTimeOffset from a string. The string must specify a
  449. // date and optionally a time in a culture-specific or universal format.
  450. // Leading and trailing whitespace characters are allowed.
  451. //
  452. public static DateTimeOffset ParseExact(string input, string format, IFormatProvider? formatProvider, DateTimeStyles styles)
  453. {
  454. styles = ValidateStyles(styles, nameof(styles));
  455. if (input == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.input);
  456. if (format == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.format);
  457. TimeSpan offset;
  458. DateTime dateResult = DateTimeParse.ParseExact(input,
  459. format,
  460. DateTimeFormatInfo.GetInstance(formatProvider),
  461. styles,
  462. out offset);
  463. return new DateTimeOffset(dateResult.Ticks, offset);
  464. }
  465. public static DateTimeOffset ParseExact(ReadOnlySpan<char> input, ReadOnlySpan<char> format, IFormatProvider? formatProvider, DateTimeStyles styles = DateTimeStyles.None)
  466. {
  467. styles = ValidateStyles(styles, nameof(styles));
  468. DateTime dateResult = DateTimeParse.ParseExact(input, format, DateTimeFormatInfo.GetInstance(formatProvider), styles, out TimeSpan offset);
  469. return new DateTimeOffset(dateResult.Ticks, offset);
  470. }
  471. public static DateTimeOffset ParseExact(string input, string[] formats, IFormatProvider? formatProvider, DateTimeStyles styles)
  472. {
  473. styles = ValidateStyles(styles, nameof(styles));
  474. if (input == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.input);
  475. TimeSpan offset;
  476. DateTime dateResult = DateTimeParse.ParseExactMultiple(input,
  477. formats,
  478. DateTimeFormatInfo.GetInstance(formatProvider),
  479. styles,
  480. out offset);
  481. return new DateTimeOffset(dateResult.Ticks, offset);
  482. }
  483. public static DateTimeOffset ParseExact(ReadOnlySpan<char> input, string[] formats, IFormatProvider? formatProvider, DateTimeStyles styles = DateTimeStyles.None)
  484. {
  485. styles = ValidateStyles(styles, nameof(styles));
  486. DateTime dateResult = DateTimeParse.ParseExactMultiple(input, formats, DateTimeFormatInfo.GetInstance(formatProvider), styles, out TimeSpan offset);
  487. return new DateTimeOffset(dateResult.Ticks, offset);
  488. }
  489. public TimeSpan Subtract(DateTimeOffset value) =>
  490. UtcDateTime.Subtract(value.UtcDateTime);
  491. public DateTimeOffset Subtract(TimeSpan value) =>
  492. new DateTimeOffset(ClockDateTime.Subtract(value), Offset);
  493. public long ToFileTime() => UtcDateTime.ToFileTime();
  494. public long ToUnixTimeSeconds()
  495. {
  496. // Truncate sub-second precision before offsetting by the Unix Epoch to avoid
  497. // the last digit being off by one for dates that result in negative Unix times.
  498. //
  499. // For example, consider the DateTimeOffset 12/31/1969 12:59:59.001 +0
  500. // ticks = 621355967990010000
  501. // ticksFromEpoch = ticks - DateTime.UnixEpochTicks = -9990000
  502. // secondsFromEpoch = ticksFromEpoch / TimeSpan.TicksPerSecond = 0
  503. //
  504. // Notice that secondsFromEpoch is rounded *up* by the truncation induced by integer division,
  505. // whereas we actually always want to round *down* when converting to Unix time. This happens
  506. // automatically for positive Unix time values. Now the example becomes:
  507. // seconds = ticks / TimeSpan.TicksPerSecond = 62135596799
  508. // secondsFromEpoch = seconds - UnixEpochSeconds = -1
  509. //
  510. // In other words, we want to consistently round toward the time 1/1/0001 00:00:00,
  511. // rather than toward the Unix Epoch (1/1/1970 00:00:00).
  512. long seconds = UtcDateTime.Ticks / TimeSpan.TicksPerSecond;
  513. return seconds - UnixEpochSeconds;
  514. }
  515. public long ToUnixTimeMilliseconds()
  516. {
  517. // Truncate sub-millisecond precision before offsetting by the Unix Epoch to avoid
  518. // the last digit being off by one for dates that result in negative Unix times
  519. long milliseconds = UtcDateTime.Ticks / TimeSpan.TicksPerMillisecond;
  520. return milliseconds - UnixEpochMilliseconds;
  521. }
  522. public DateTimeOffset ToLocalTime() =>
  523. ToLocalTime(false);
  524. internal DateTimeOffset ToLocalTime(bool throwOnOverflow) =>
  525. ToLocalTime(UtcDateTime, throwOnOverflow);
  526. private static DateTimeOffset ToLocalTime(DateTime utcDateTime, bool throwOnOverflow)
  527. {
  528. TimeSpan offset = TimeZoneInfo.GetLocalUtcOffset(utcDateTime, TimeZoneInfoOptions.NoThrowOnInvalidTime);
  529. long localTicks = utcDateTime.Ticks + offset.Ticks;
  530. if (localTicks < DateTime.MinTicks || localTicks > DateTime.MaxTicks)
  531. {
  532. if (throwOnOverflow)
  533. throw new ArgumentException(SR.Arg_ArgumentOutOfRangeException);
  534. localTicks = localTicks < DateTime.MinTicks ? DateTime.MinTicks : DateTime.MaxTicks;
  535. }
  536. return new DateTimeOffset(localTicks, offset);
  537. }
  538. public override string ToString() =>
  539. DateTimeFormat.Format(ClockDateTime, null, null, Offset);
  540. public string ToString(string? format) =>
  541. DateTimeFormat.Format(ClockDateTime, format, null, Offset);
  542. public string ToString(IFormatProvider? formatProvider) =>
  543. DateTimeFormat.Format(ClockDateTime, null, formatProvider, Offset);
  544. public string ToString(string? format, IFormatProvider? formatProvider) =>
  545. DateTimeFormat.Format(ClockDateTime, format, formatProvider, Offset);
  546. public bool TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format = default, IFormatProvider? formatProvider = null) =>
  547. DateTimeFormat.TryFormat(ClockDateTime, destination, out charsWritten, format, formatProvider, Offset);
  548. public DateTimeOffset ToUniversalTime() =>
  549. new DateTimeOffset(UtcDateTime);
  550. public static bool TryParse(string? input, out DateTimeOffset result)
  551. {
  552. TimeSpan offset;
  553. DateTime dateResult;
  554. bool parsed = DateTimeParse.TryParse(input,
  555. DateTimeFormatInfo.CurrentInfo,
  556. DateTimeStyles.None,
  557. out dateResult,
  558. out offset);
  559. result = new DateTimeOffset(dateResult.Ticks, offset);
  560. return parsed;
  561. }
  562. public static bool TryParse(ReadOnlySpan<char> input, out DateTimeOffset result)
  563. {
  564. bool parsed = DateTimeParse.TryParse(input, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.None, out DateTime dateResult, out TimeSpan offset);
  565. result = new DateTimeOffset(dateResult.Ticks, offset);
  566. return parsed;
  567. }
  568. public static bool TryParse(string? input, IFormatProvider? formatProvider, DateTimeStyles styles, out DateTimeOffset result)
  569. {
  570. styles = ValidateStyles(styles, nameof(styles));
  571. if (input == null)
  572. {
  573. result = default;
  574. return false;
  575. }
  576. TimeSpan offset;
  577. DateTime dateResult;
  578. bool parsed = DateTimeParse.TryParse(input,
  579. DateTimeFormatInfo.GetInstance(formatProvider),
  580. styles,
  581. out dateResult,
  582. out offset);
  583. result = new DateTimeOffset(dateResult.Ticks, offset);
  584. return parsed;
  585. }
  586. public static bool TryParse(ReadOnlySpan<char> input, IFormatProvider? formatProvider, DateTimeStyles styles, out DateTimeOffset result)
  587. {
  588. styles = ValidateStyles(styles, nameof(styles));
  589. bool parsed = DateTimeParse.TryParse(input, DateTimeFormatInfo.GetInstance(formatProvider), styles, out DateTime dateResult, out TimeSpan offset);
  590. result = new DateTimeOffset(dateResult.Ticks, offset);
  591. return parsed;
  592. }
  593. public static bool TryParseExact(string? input, string? format, IFormatProvider? formatProvider, DateTimeStyles styles,
  594. out DateTimeOffset result)
  595. {
  596. styles = ValidateStyles(styles, nameof(styles));
  597. if (input == null || format == null)
  598. {
  599. result = default;
  600. return false;
  601. }
  602. TimeSpan offset;
  603. DateTime dateResult;
  604. bool parsed = DateTimeParse.TryParseExact(input,
  605. format,
  606. DateTimeFormatInfo.GetInstance(formatProvider),
  607. styles,
  608. out dateResult,
  609. out offset);
  610. result = new DateTimeOffset(dateResult.Ticks, offset);
  611. return parsed;
  612. }
  613. public static bool TryParseExact(
  614. ReadOnlySpan<char> input, ReadOnlySpan<char> format, IFormatProvider? formatProvider, DateTimeStyles styles, out DateTimeOffset result)
  615. {
  616. styles = ValidateStyles(styles, nameof(styles));
  617. bool parsed = DateTimeParse.TryParseExact(input, format, DateTimeFormatInfo.GetInstance(formatProvider), styles, out DateTime dateResult, out TimeSpan offset);
  618. result = new DateTimeOffset(dateResult.Ticks, offset);
  619. return parsed;
  620. }
  621. public static bool TryParseExact(string? input, string?[]? formats, IFormatProvider? formatProvider, DateTimeStyles styles,
  622. out DateTimeOffset result)
  623. {
  624. styles = ValidateStyles(styles, nameof(styles));
  625. if (input == null)
  626. {
  627. result = default;
  628. return false;
  629. }
  630. TimeSpan offset;
  631. DateTime dateResult;
  632. bool parsed = DateTimeParse.TryParseExactMultiple(input,
  633. formats,
  634. DateTimeFormatInfo.GetInstance(formatProvider),
  635. styles,
  636. out dateResult,
  637. out offset);
  638. result = new DateTimeOffset(dateResult.Ticks, offset);
  639. return parsed;
  640. }
  641. public static bool TryParseExact(
  642. ReadOnlySpan<char> input, string?[]? formats, IFormatProvider? formatProvider, DateTimeStyles styles, out DateTimeOffset result)
  643. {
  644. styles = ValidateStyles(styles, nameof(styles));
  645. bool parsed = DateTimeParse.TryParseExactMultiple(input, formats, DateTimeFormatInfo.GetInstance(formatProvider), styles, out DateTime dateResult, out TimeSpan offset);
  646. result = new DateTimeOffset(dateResult.Ticks, offset);
  647. return parsed;
  648. }
  649. // Ensures the TimeSpan is valid to go in a DateTimeOffset.
  650. private static short ValidateOffset(TimeSpan offset)
  651. {
  652. long ticks = offset.Ticks;
  653. if (ticks % TimeSpan.TicksPerMinute != 0)
  654. {
  655. throw new ArgumentException(SR.Argument_OffsetPrecision, nameof(offset));
  656. }
  657. if (ticks < MinOffset || ticks > MaxOffset)
  658. {
  659. throw new ArgumentOutOfRangeException(nameof(offset), SR.Argument_OffsetOutOfRange);
  660. }
  661. return (short)(offset.Ticks / TimeSpan.TicksPerMinute);
  662. }
  663. // Ensures that the time and offset are in range.
  664. private static DateTime ValidateDate(DateTime dateTime, TimeSpan offset)
  665. {
  666. // The key validation is that both the UTC and clock times fit. The clock time is validated
  667. // by the DateTime constructor.
  668. Debug.Assert(offset.Ticks >= MinOffset && offset.Ticks <= MaxOffset, "Offset not validated.");
  669. // This operation cannot overflow because offset should have already been validated to be within
  670. // 14 hours and the DateTime instance is more than that distance from the boundaries of long.
  671. long utcTicks = dateTime.Ticks - offset.Ticks;
  672. if (utcTicks < DateTime.MinTicks || utcTicks > DateTime.MaxTicks)
  673. {
  674. throw new ArgumentOutOfRangeException(nameof(offset), SR.Argument_UTCOutOfRange);
  675. }
  676. // make sure the Kind is set to Unspecified
  677. //
  678. return new DateTime(utcTicks, DateTimeKind.Unspecified);
  679. }
  680. private static DateTimeStyles ValidateStyles(DateTimeStyles style, string parameterName)
  681. {
  682. if ((style & DateTimeFormatInfo.InvalidDateTimeStyles) != 0)
  683. {
  684. throw new ArgumentException(SR.Argument_InvalidDateTimeStyles, parameterName);
  685. }
  686. if (((style & (DateTimeStyles.AssumeLocal)) != 0) && ((style & (DateTimeStyles.AssumeUniversal)) != 0))
  687. {
  688. throw new ArgumentException(SR.Argument_ConflictingDateTimeStyles, parameterName);
  689. }
  690. if ((style & DateTimeStyles.NoCurrentDateDefault) != 0)
  691. {
  692. throw new ArgumentException(SR.Argument_DateTimeOffsetInvalidDateTimeStyles, parameterName);
  693. }
  694. // RoundtripKind does not make sense for DateTimeOffset; ignore this flag for backward compatibility with DateTime
  695. style &= ~DateTimeStyles.RoundtripKind;
  696. // AssumeLocal is also ignored as that is what we do by default with DateTimeOffset.Parse
  697. style &= ~DateTimeStyles.AssumeLocal;
  698. return style;
  699. }
  700. // Operators
  701. public static implicit operator DateTimeOffset(DateTime dateTime) =>
  702. new DateTimeOffset(dateTime);
  703. public static DateTimeOffset operator +(DateTimeOffset dateTimeOffset, TimeSpan timeSpan) =>
  704. new DateTimeOffset(dateTimeOffset.ClockDateTime + timeSpan, dateTimeOffset.Offset);
  705. public static DateTimeOffset operator -(DateTimeOffset dateTimeOffset, TimeSpan timeSpan) =>
  706. new DateTimeOffset(dateTimeOffset.ClockDateTime - timeSpan, dateTimeOffset.Offset);
  707. public static TimeSpan operator -(DateTimeOffset left, DateTimeOffset right) =>
  708. left.UtcDateTime - right.UtcDateTime;
  709. public static bool operator ==(DateTimeOffset left, DateTimeOffset right) =>
  710. left.UtcDateTime == right.UtcDateTime;
  711. public static bool operator !=(DateTimeOffset left, DateTimeOffset right) =>
  712. left.UtcDateTime != right.UtcDateTime;
  713. public static bool operator <(DateTimeOffset left, DateTimeOffset right) =>
  714. left.UtcDateTime < right.UtcDateTime;
  715. public static bool operator <=(DateTimeOffset left, DateTimeOffset right) =>
  716. left.UtcDateTime <= right.UtcDateTime;
  717. public static bool operator >(DateTimeOffset left, DateTimeOffset right) =>
  718. left.UtcDateTime > right.UtcDateTime;
  719. public static bool operator >=(DateTimeOffset left, DateTimeOffset right) =>
  720. left.UtcDateTime >= right.UtcDateTime;
  721. }
  722. }