1 //------------------------------------------------------------------------------
2 // <copyright file="XsdDuration.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 // <owner current="true" primary="true">Microsoft</owner>
6 //------------------------------------------------------------------------------
8 namespace System.Xml.Schema {
10 using System.Diagnostics;
14 /// This structure holds components of an Xsd Duration. It is used internally to support Xsd durations without loss
15 /// of fidelity. XsdDuration structures are immutable once they've been created.
18 [System.Runtime.CompilerServices.FriendAccessAllowed] // used by System.Runtime.Serialization.dll
20 internal struct XsdDuration {
27 private uint nanoseconds; // High bit is used to indicate whether duration is negative
29 private const uint NegativeBit = 0x80000000;
41 public enum DurationType {
48 /// Construct an XsdDuration from component parts.
50 public XsdDuration(bool isNegative, int years, int months, int days, int hours, int minutes, int seconds, int nanoseconds) {
51 if (years < 0) throw new ArgumentOutOfRangeException("years");
52 if (months < 0) throw new ArgumentOutOfRangeException("months");
53 if (days < 0) throw new ArgumentOutOfRangeException("days");
54 if (hours < 0) throw new ArgumentOutOfRangeException("hours");
55 if (minutes < 0) throw new ArgumentOutOfRangeException("minutes");
56 if (seconds < 0) throw new ArgumentOutOfRangeException("seconds");
57 if (nanoseconds < 0 || nanoseconds > 999999999) throw new ArgumentOutOfRangeException("nanoseconds");
63 this.minutes = minutes;
64 this.seconds = seconds;
65 this.nanoseconds = (uint) nanoseconds;
68 this.nanoseconds |= NegativeBit;
72 /// Construct an XsdDuration from a TimeSpan value.
74 public XsdDuration(TimeSpan timeSpan) : this(timeSpan, DurationType.Duration) {
78 /// Construct an XsdDuration from a TimeSpan value that represents an xsd:duration, an xdt:dayTimeDuration, or
79 /// an xdt:yearMonthDuration.
81 public XsdDuration(TimeSpan timeSpan, DurationType durationType) {
82 long ticks = timeSpan.Ticks;
87 // Note that (ulong) -Int64.MinValue = Int64.MaxValue + 1, which is what we want for that special case
89 ticksPos = (ulong) -ticks;
93 ticksPos = (ulong) ticks;
96 if (durationType == DurationType.YearMonthDuration) {
97 int years = (int) (ticksPos / ((ulong) TimeSpan.TicksPerDay * 365));
98 int months = (int) ((ticksPos % ((ulong) TimeSpan.TicksPerDay * 365)) / ((ulong) TimeSpan.TicksPerDay * 30));
101 // If remaining days >= 360 and < 365, then round off to year
106 this = new XsdDuration(isNegative, years, months, 0, 0, 0, 0, 0);
109 Debug.Assert(durationType == DurationType.Duration || durationType == DurationType.DayTimeDuration);
111 // Tick count is expressed in 100 nanosecond intervals
112 this.nanoseconds = (uint) (ticksPos % 10000000) * 100;
114 this.nanoseconds |= NegativeBit;
118 this.days = (int) (ticksPos / (ulong) TimeSpan.TicksPerDay);
119 this.hours = (int) ((ticksPos / (ulong) TimeSpan.TicksPerHour) % 24);
120 this.minutes = (int) ((ticksPos / (ulong) TimeSpan.TicksPerMinute) % 60);
121 this.seconds = (int) ((ticksPos / (ulong) TimeSpan.TicksPerSecond) % 60);
126 /// Constructs an XsdDuration from a string in the xsd:duration format. Components are stored with loss
127 /// of fidelity (except in the case of overflow).
129 public XsdDuration(string s) : this(s, DurationType.Duration) {
133 /// Constructs an XsdDuration from a string in the xsd:duration format. Components are stored without loss
134 /// of fidelity (except in the case of overflow).
136 public XsdDuration(string s, DurationType durationType) {
138 Exception exception = TryParse(s, durationType, out result);
139 if (exception != null) {
142 this.years = result.Years;
143 this.months = result.Months;
144 this.days = result.Days;
145 this.hours = result.Hours;
146 this.minutes = result.Minutes;
147 this.seconds = result.Seconds;
148 this.nanoseconds = (uint)result.Nanoseconds;
149 if (result.IsNegative) {
150 this.nanoseconds |= NegativeBit;
156 /// Return true if this duration is negative.
158 public bool IsNegative {
159 get { return (this.nanoseconds & NegativeBit) != 0; }
163 /// Return number of years in this duration (stored in 31 bits).
166 get { return this.years; }
170 /// Return number of months in this duration (stored in 31 bits).
173 get { return this.months; }
177 /// Return number of days in this duration (stored in 31 bits).
180 get { return this.days; }
184 /// Return number of hours in this duration (stored in 31 bits).
187 get { return this.hours; }
191 /// Return number of minutes in this duration (stored in 31 bits).
194 get { return this.minutes; }
198 /// Return number of seconds in this duration (stored in 31 bits).
201 get { return this.seconds; }
205 /// Return number of nanoseconds in this duration.
207 public int Nanoseconds {
208 get { return (int) (this.nanoseconds & ~NegativeBit); }
213 /// Return number of microseconds in this duration.
215 public int Microseconds {
216 get { return Nanoseconds / 1000; }
220 /// Return number of milliseconds in this duration.
222 public int Milliseconds {
223 get { return Nanoseconds / 1000000; }
227 /// Normalize year-month part and day-time part so that month < 12, hour < 24, minute < 60, and second < 60.
229 public XsdDuration Normalize() {
234 int minutes = Minutes;
235 int seconds = Seconds;
240 years += months / 12;
245 minutes += seconds / 60;
250 hours += minutes / 60;
260 catch (OverflowException) {
261 throw new OverflowException(Res.GetString(Res.XmlConvert_Overflow, ToString(), "Duration"));
264 return new XsdDuration(IsNegative, years, months, days, hours, minutes, seconds, Nanoseconds);
269 /// Internal helper method that converts an Xsd duration to a TimeSpan value. This code uses the estimate
270 /// that there are 365 days in the year and 30 days in a month.
272 public TimeSpan ToTimeSpan() {
273 return ToTimeSpan(DurationType.Duration);
277 /// Internal helper method that converts an Xsd duration to a TimeSpan value. This code uses the estimate
278 /// that there are 365 days in the year and 30 days in a month.
280 public TimeSpan ToTimeSpan(DurationType durationType) {
282 Exception exception = TryToTimeSpan(durationType, out result);
283 if (exception != null) {
290 internal Exception TryToTimeSpan(out TimeSpan result) {
291 return TryToTimeSpan(DurationType.Duration, out result);
295 internal Exception TryToTimeSpan(DurationType durationType, out TimeSpan result) {
296 Exception exception = null;
299 // Throw error if result cannot fit into a long
302 // Discard year and month parts if constructing TimeSpan for DayTimeDuration
303 if (durationType != DurationType.DayTimeDuration) {
304 ticks += ((ulong) this.years + (ulong) this.months / 12) * 365;
305 ticks += ((ulong) this.months % 12) * 30;
308 // Discard day and time parts if constructing TimeSpan for YearMonthDuration
309 if (durationType != DurationType.YearMonthDuration) {
310 ticks += (ulong) this.days;
313 ticks += (ulong) this.hours;
316 ticks += (ulong) this.minutes;
319 ticks += (ulong) this.seconds;
321 // Tick count interval is in 100 nanosecond intervals (7 digits)
322 ticks *= (ulong) TimeSpan.TicksPerSecond;
323 ticks += (ulong) Nanoseconds / 100;
326 // Multiply YearMonth duration by number of ticks per day
327 ticks *= (ulong) TimeSpan.TicksPerDay;
331 // Handle special case of Int64.MaxValue + 1 before negation, since it would otherwise overflow
332 if (ticks == (ulong) Int64.MaxValue + 1) {
333 result = new TimeSpan(Int64.MinValue);
336 result = new TimeSpan(-((long) ticks));
340 result = new TimeSpan((long) ticks);
345 catch (OverflowException) {
346 result = TimeSpan.MinValue;
347 exception = new OverflowException(Res.GetString(Res.XmlConvert_Overflow, durationType, "TimeSpan"));
353 /// Return the string representation of this Xsd duration.
355 public override string ToString() {
356 return ToString(DurationType.Duration);
360 /// Return the string representation according to xsd:duration rules, xdt:dayTimeDuration rules, or
361 /// xdt:yearMonthDuration rules.
363 internal string ToString(DurationType durationType) {
364 StringBuilder sb = new StringBuilder(20);
365 int nanoseconds, digit, zeroIdx, len;
372 if (durationType != DurationType.DayTimeDuration) {
374 if (this.years != 0) {
375 sb.Append(XmlConvert.ToString(this.years));
379 if (this.months != 0) {
380 sb.Append(XmlConvert.ToString(this.months));
385 if (durationType != DurationType.YearMonthDuration) {
386 if (this.days != 0) {
387 sb.Append(XmlConvert.ToString(this.days));
391 if (this.hours != 0 || this.minutes != 0 || this.seconds != 0 || Nanoseconds != 0) {
393 if (this.hours != 0) {
394 sb.Append(XmlConvert.ToString(this.hours));
398 if (this.minutes != 0) {
399 sb.Append(XmlConvert.ToString(this.minutes));
403 nanoseconds = Nanoseconds;
404 if (this.seconds != 0 || nanoseconds != 0) {
405 sb.Append(XmlConvert.ToString(this.seconds));
406 if (nanoseconds != 0) {
411 zeroIdx = sb.Length - 1;
413 for (int idx = zeroIdx; idx >= len; idx--) {
414 digit = nanoseconds % 10;
415 sb[idx] = (char) (digit + '0');
417 if (zeroIdx == idx && digit == 0)
423 sb.Length = zeroIdx + 1;
429 // Zero is represented as "PT0S"
430 if (sb[sb.Length - 1] == 'P')
434 // Zero is represented as "T0M"
435 if (sb[sb.Length - 1] == 'P')
439 return sb.ToString();
443 internal static Exception TryParse(string s, out XsdDuration result) {
444 return TryParse(s, DurationType.Duration, out result);
448 internal static Exception TryParse(string s, DurationType durationType, out XsdDuration result) {
451 int value, pos, numDigits;
452 Parts parts = Parts.HasNone;
454 result = new XsdDuration();
462 if (pos >= length) goto InvalidFormat;
466 result.nanoseconds = NegativeBit;
469 result.nanoseconds = 0;
472 if (pos >= length) goto InvalidFormat;
474 if (s[pos++] != 'P') goto InvalidFormat;
476 errorCode = TryParseDigits(s, ref pos, false, out value, out numDigits);
477 if (errorCode != null) goto Error;
479 if (pos >= length) goto InvalidFormat;
482 if (numDigits == 0) goto InvalidFormat;
484 parts |= Parts.HasYears;
485 result.years = value;
486 if (++pos == length) goto Done;
488 errorCode = TryParseDigits(s, ref pos, false, out value, out numDigits);
489 if (errorCode != null) goto Error;
491 if (pos >= length) goto InvalidFormat;
495 if (numDigits == 0) goto InvalidFormat;
497 parts |= Parts.HasMonths;
498 result.months = value;
499 if (++pos == length) goto Done;
501 errorCode = TryParseDigits(s, ref pos, false, out value, out numDigits);
502 if (errorCode != null) goto Error;
504 if (pos >= length) goto InvalidFormat;
508 if (numDigits == 0) goto InvalidFormat;
510 parts |= Parts.HasDays;
512 if (++pos == length) goto Done;
514 errorCode = TryParseDigits(s, ref pos, false, out value, out numDigits);
515 if (errorCode != null) goto Error;
517 if (pos >= length) goto InvalidFormat;
521 if (numDigits != 0) goto InvalidFormat;
524 errorCode = TryParseDigits(s, ref pos, false, out value, out numDigits);
525 if (errorCode != null) goto Error;
527 if (pos >= length) goto InvalidFormat;
530 if (numDigits == 0) goto InvalidFormat;
532 parts |= Parts.HasHours;
533 result.hours = value;
534 if (++pos == length) goto Done;
536 errorCode = TryParseDigits(s, ref pos, false, out value, out numDigits);
537 if (errorCode != null) goto Error;
539 if (pos >= length) goto InvalidFormat;
543 if (numDigits == 0) goto InvalidFormat;
545 parts |= Parts.HasMinutes;
546 result.minutes = value;
547 if (++pos == length) goto Done;
549 errorCode = TryParseDigits(s, ref pos, false, out value, out numDigits);
550 if (errorCode != null) goto Error;
552 if (pos >= length) goto InvalidFormat;
558 parts |= Parts.HasSeconds;
559 result.seconds = value;
561 errorCode = TryParseDigits(s, ref pos, true, out value, out numDigits);
562 if (errorCode != null) goto Error;
564 if (numDigits == 0) { //If there are no digits after the decimal point, assume 0
567 // Normalize to nanosecond intervals
568 for (; numDigits > 9; numDigits--)
571 for (; numDigits < 9; numDigits++)
574 result.nanoseconds |= (uint) value;
576 if (pos >= length) goto InvalidFormat;
578 if (s[pos] != 'S') goto InvalidFormat;
579 if (++pos == length) goto Done;
581 else if (s[pos] == 'S') {
582 if (numDigits == 0) goto InvalidFormat;
584 parts |= Parts.HasSeconds;
585 result.seconds = value;
586 if (++pos == length) goto Done;
590 // Duration cannot end with digits
591 if (numDigits != 0) goto InvalidFormat;
593 // No further characters are allowed
594 if (pos != length) goto InvalidFormat;
597 // At least one part must be defined
598 if (parts == Parts.HasNone) goto InvalidFormat;
600 if (durationType == DurationType.DayTimeDuration) {
601 if ((parts & (Parts.HasYears | Parts.HasMonths)) != 0)
604 else if (durationType == DurationType.YearMonthDuration) {
605 if ((parts & ~(XsdDuration.Parts.HasYears | XsdDuration.Parts.HasMonths)) != 0)
611 return new FormatException(Res.GetString(Res.XmlConvert_BadFormat, s, durationType));
614 return new OverflowException(Res.GetString(Res.XmlConvert_Overflow, s, durationType));
617 /// Helper method that constructs an integer from leading digits starting at s[offset]. "offset" is
618 /// updated to contain an offset just beyond the last digit. The number of digits consumed is returned in
619 /// cntDigits. The integer is returned (0 if no digits). If the digits cannot fit into an Int32:
620 /// 1. If eatDigits is true, then additional digits will be silently discarded (don't count towards numDigits)
621 /// 2. If eatDigits is false, an overflow exception is thrown
622 private static string TryParseDigits(string s, ref int offset, bool eatDigits, out int result, out int numDigits) {
623 int offsetStart = offset;
624 int offsetEnd = s.Length;
630 while (offset < offsetEnd && s[offset] >= '0' && s[offset] <= '9') {
631 digit = s[offset] - '0';
633 if (result > (Int32.MaxValue - digit) / 10) {
635 return Res.XmlConvert_Overflow;
638 // Skip past any remaining digits
639 numDigits = offset - offsetStart;
641 while (offset < offsetEnd && s[offset] >= '0' && s[offset] <= '9') {
648 result = result * 10 + digit;
652 numDigits = offset - offsetStart;