TimeSpanParse.cs 64 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711
  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. ////////////////////////////////////////////////////////////////////////////
  5. //
  6. // Purpose: Used by TimeSpan to parse a time interval string.
  7. //
  8. // Standard Format:
  9. // -=-=-=-=-=-=-=-
  10. // "c": Constant format. [-][d'.']hh':'mm':'ss['.'fffffff]
  11. // Not culture sensitive. Default format (and null/empty format string) map to this format.
  12. //
  13. // "g": General format, short: [-][d':']h':'mm':'ss'.'FFFFFFF
  14. // Only print what's needed. Localized (if you want Invariant, pass in Invariant).
  15. // The fractional seconds separator is localized, equal to the culture's DecimalSeparator.
  16. //
  17. // "G": General format, long: [-]d':'hh':'mm':'ss'.'fffffff
  18. // Always print days and 7 fractional digits. Localized (if you want Invariant, pass in Invariant).
  19. // The fractional seconds separator is localized, equal to the culture's DecimalSeparator.
  20. //
  21. // * "TryParseTimeSpan" is the main method for Parse/TryParse
  22. //
  23. // - TimeSpanTokenizer.GetNextToken() is used to split the input string into number and literal tokens.
  24. // - TimeSpanRawInfo.ProcessToken() adds the next token into the parsing intermediary state structure
  25. // - ProcessTerminalState() uses the fully initialized TimeSpanRawInfo to find a legal parse match.
  26. // The terminal states are attempted as follows:
  27. // foreach (+InvariantPattern, -InvariantPattern, +LocalizedPattern, -LocalizedPattern) try
  28. // 1 number => d
  29. // 2 numbers => h:m
  30. // 3 numbers => h:m:s | d.h:m | h:m:.f
  31. // 4 numbers => h:m:s.f | d.h:m:s | d.h:m:.f
  32. // 5 numbers => d.h:m:s.f
  33. //
  34. // Custom Format:
  35. // -=-=-=-=-=-=-=
  36. //
  37. // * "TryParseExactTimeSpan" is the main method for ParseExact/TryParseExact methods
  38. // * "TryParseExactMultipleTimeSpan" is the main method for ParseExact/TryparseExact
  39. // methods that take a string[] of formats
  40. //
  41. // - For single-letter formats "TryParseTimeSpan" is called (see above)
  42. // - For multi-letter formats "TryParseByFormat" is called
  43. // - TryParseByFormat uses helper methods (ParseExactLiteral, ParseExactDigits, etc)
  44. // which drive the underlying TimeSpanTokenizer. However, unlike standard formatting which
  45. // operates on whole-tokens, ParseExact operates at the character-level. As such,
  46. // TimeSpanTokenizer.NextChar and TimeSpanTokenizer.BackOne() are called directly.
  47. //
  48. ////////////////////////////////////////////////////////////////////////////
  49. using System.Diagnostics;
  50. using System.Text;
  51. namespace System.Globalization
  52. {
  53. internal static class TimeSpanParse
  54. {
  55. private const int MaxFractionDigits = 7;
  56. private const int MaxDays = 10675199;
  57. private const int MaxHours = 23;
  58. private const int MaxMinutes = 59;
  59. private const int MaxSeconds = 59;
  60. private const int MaxFraction = 9999999;
  61. [Flags]
  62. private enum TimeSpanStandardStyles : byte
  63. {
  64. // Standard Format Styles
  65. None = 0x00000000,
  66. Invariant = 0x00000001, //Allow Invariant Culture
  67. Localized = 0x00000002, //Allow Localized Culture
  68. RequireFull = 0x00000004, //Require the input to be in DHMSF format
  69. Any = Invariant | Localized,
  70. }
  71. // TimeSpan Token Types
  72. private enum TTT : byte
  73. {
  74. None = 0, // None of the TimeSpanToken fields are set
  75. End = 1, // '\0'
  76. Num = 2, // Number
  77. Sep = 3, // literal
  78. NumOverflow = 4, // Number that overflowed
  79. }
  80. private ref struct TimeSpanToken
  81. {
  82. internal TTT _ttt;
  83. internal int _num; // Store the number that we are parsing (if any)
  84. internal int _zeroes; // Store the number of leading zeroes (if any)
  85. internal ReadOnlySpan<char> _sep; // Store the literal that we are parsing (if any)
  86. public TimeSpanToken(TTT type) : this(type, 0, 0, default) { }
  87. public TimeSpanToken(int number) : this(TTT.Num, number, 0, default) { }
  88. public TimeSpanToken(int number, int leadingZeroes) : this(TTT.Num, number, leadingZeroes, default) { }
  89. public TimeSpanToken(TTT type, int number, int leadingZeroes, ReadOnlySpan<char> separator)
  90. {
  91. _ttt = type;
  92. _num = number;
  93. _zeroes = leadingZeroes;
  94. _sep = separator;
  95. }
  96. public bool NormalizeAndValidateFraction()
  97. {
  98. Debug.Assert(_ttt == TTT.Num);
  99. Debug.Assert(_num > -1);
  100. if (_num == 0)
  101. return true;
  102. if (_zeroes == 0 && _num > MaxFraction)
  103. return false;
  104. int totalDigitsCount = ((int) Math.Floor(Math.Log10(_num))) + 1 + _zeroes;
  105. if (totalDigitsCount == MaxFractionDigits)
  106. {
  107. // Already normalized. no more action needed
  108. // .9999999 normalize to 9,999,999 ticks
  109. // .0000001 normalize to 1 ticks
  110. return true;
  111. }
  112. if (totalDigitsCount < MaxFractionDigits)
  113. {
  114. // normalize the fraction to the 7-digits
  115. // .999999 normalize to 9,999,990 ticks
  116. // .99999 normalize to 9,999,900 ticks
  117. // .000001 normalize to 10 ticks
  118. // .1 normalize to 1,000,000 ticks
  119. _num *= (int) Pow10(MaxFractionDigits - totalDigitsCount);
  120. return true;
  121. }
  122. // totalDigitsCount is greater then MaxFractionDigits, we'll need to do the rounding to 7-digits length
  123. // .00000001 normalized to 0 ticks
  124. // .00000005 normalized to 1 ticks
  125. // .09999999 normalize to 1,000,000 ticks
  126. // .099999999 normalize to 1,000,000 ticks
  127. Debug.Assert(_zeroes > 0); // Already validated that in the condition _zeroes == 0 && _num > MaxFraction
  128. _num = (int) Math.Round((double)_num / Pow10(totalDigitsCount - MaxFractionDigits), MidpointRounding.AwayFromZero);
  129. Debug.Assert(_num < MaxFraction);
  130. return true;
  131. }
  132. }
  133. private ref struct TimeSpanTokenizer
  134. {
  135. private ReadOnlySpan<char> _value;
  136. private int _pos;
  137. internal TimeSpanTokenizer(ReadOnlySpan<char> input) : this(input, 0) { }
  138. internal TimeSpanTokenizer(ReadOnlySpan<char> input, int startPosition)
  139. {
  140. _value = input;
  141. _pos = startPosition;
  142. }
  143. /// <summary>Returns the next token in the input string</summary>
  144. /// <remarks>Used by the parsing routines that operate on standard-formats.</remarks>
  145. internal TimeSpanToken GetNextToken()
  146. {
  147. // Get the position of the next character to be processed. If there is no
  148. // next character, we're at the end.
  149. int pos = _pos;
  150. Debug.Assert(pos > -1);
  151. if (pos >= _value.Length)
  152. {
  153. return new TimeSpanToken(TTT.End);
  154. }
  155. // Now retrieve that character. If it's a digit, we're processing a number.
  156. int num = _value[pos] - '0';
  157. if ((uint)num <= 9)
  158. {
  159. int zeroes = 0;
  160. if (num == 0)
  161. {
  162. // Read all leading zeroes.
  163. zeroes = 1;
  164. while (true)
  165. {
  166. int digit;
  167. if (++_pos >= _value.Length || (uint)(digit = _value[_pos] - '0') > 9)
  168. {
  169. return new TimeSpanToken(TTT.Num, 0, zeroes, default);
  170. }
  171. if (digit == 0)
  172. {
  173. zeroes++;
  174. continue;
  175. }
  176. num = digit;
  177. break;
  178. }
  179. }
  180. // Continue to read as long as we're reading digits.
  181. while (++_pos < _value.Length)
  182. {
  183. int digit = _value[_pos] - '0';
  184. if ((uint)digit > 9)
  185. {
  186. break;
  187. }
  188. num = num * 10 + digit;
  189. if ((num & 0xF0000000) != 0) // Max limit we can support 268435455 which is FFFFFFF
  190. {
  191. return new TimeSpanToken(TTT.NumOverflow);
  192. }
  193. }
  194. return new TimeSpanToken(TTT.Num, num, zeroes, default);
  195. }
  196. // Otherwise, we're processing a separator, and we've already processed the first
  197. // character of it. Continue processing characters as long as they're not digits.
  198. int length = 1;
  199. while (true)
  200. {
  201. if (++_pos >= _value.Length || (uint)(_value[_pos] - '0') <= 9)
  202. {
  203. break;
  204. }
  205. length++;
  206. }
  207. // Return the separator.
  208. return new TimeSpanToken(TTT.Sep, 0, 0, _value.Slice(pos, length));
  209. }
  210. internal bool EOL => _pos >= (_value.Length - 1);
  211. internal void BackOne()
  212. {
  213. if (_pos > 0) --_pos;
  214. }
  215. internal char NextChar
  216. {
  217. get
  218. {
  219. int pos = ++_pos;
  220. return (uint)pos < (uint)_value.Length ?
  221. _value[pos] :
  222. (char)0;
  223. }
  224. }
  225. }
  226. /// <summary>Stores intermediary parsing state for the standard formats.</summary>
  227. private ref struct TimeSpanRawInfo
  228. {
  229. internal TimeSpanFormat.FormatLiterals PositiveInvariant => TimeSpanFormat.PositiveInvariantFormatLiterals;
  230. internal TimeSpanFormat.FormatLiterals NegativeInvariant => TimeSpanFormat.NegativeInvariantFormatLiterals;
  231. internal TimeSpanFormat.FormatLiterals PositiveLocalized
  232. {
  233. get
  234. {
  235. if (!_posLocInit)
  236. {
  237. _posLoc = new TimeSpanFormat.FormatLiterals();
  238. _posLoc.Init(_fullPosPattern, false);
  239. _posLocInit = true;
  240. }
  241. return _posLoc;
  242. }
  243. }
  244. internal TimeSpanFormat.FormatLiterals NegativeLocalized
  245. {
  246. get
  247. {
  248. if (!_negLocInit)
  249. {
  250. _negLoc = new TimeSpanFormat.FormatLiterals();
  251. _negLoc.Init(_fullNegPattern, false);
  252. _negLocInit = true;
  253. }
  254. return _negLoc;
  255. }
  256. }
  257. internal bool FullAppCompatMatch(TimeSpanFormat.FormatLiterals pattern) =>
  258. _sepCount == 5
  259. && _numCount == 4
  260. && _literals0.EqualsOrdinal(pattern.Start)
  261. && _literals1.EqualsOrdinal(pattern.DayHourSep)
  262. && _literals2.EqualsOrdinal(pattern.HourMinuteSep)
  263. && _literals3.EqualsOrdinal(pattern.AppCompatLiteral)
  264. && _literals4.EqualsOrdinal(pattern.End);
  265. internal bool PartialAppCompatMatch(TimeSpanFormat.FormatLiterals pattern) =>
  266. _sepCount == 4
  267. && _numCount == 3
  268. && _literals0.EqualsOrdinal(pattern.Start)
  269. && _literals1.EqualsOrdinal(pattern.HourMinuteSep)
  270. && _literals2.EqualsOrdinal(pattern.AppCompatLiteral)
  271. && _literals3.EqualsOrdinal(pattern.End);
  272. /// <summary>DHMSF (all values matched)</summary>
  273. internal bool FullMatch(TimeSpanFormat.FormatLiterals pattern) =>
  274. _sepCount == MaxLiteralTokens
  275. && _numCount == MaxNumericTokens
  276. && _literals0.EqualsOrdinal(pattern.Start)
  277. && _literals1.EqualsOrdinal(pattern.DayHourSep)
  278. && _literals2.EqualsOrdinal(pattern.HourMinuteSep)
  279. && _literals3.EqualsOrdinal(pattern.MinuteSecondSep)
  280. && _literals4.EqualsOrdinal(pattern.SecondFractionSep)
  281. && _literals5.EqualsOrdinal(pattern.End);
  282. /// <summary>D (no hours, minutes, seconds, or fractions)</summary>
  283. internal bool FullDMatch(TimeSpanFormat.FormatLiterals pattern) =>
  284. _sepCount == 2
  285. && _numCount == 1
  286. && _literals0.EqualsOrdinal(pattern.Start)
  287. && _literals1.EqualsOrdinal(pattern.End);
  288. /// <summary>HM (no days, seconds, or fractions)</summary>
  289. internal bool FullHMMatch(TimeSpanFormat.FormatLiterals pattern) =>
  290. _sepCount == 3
  291. && _numCount == 2
  292. && _literals0.EqualsOrdinal(pattern.Start)
  293. && _literals1.EqualsOrdinal(pattern.HourMinuteSep)
  294. && _literals2.EqualsOrdinal(pattern.End);
  295. /// <summary>DHM (no seconds or fraction)</summary>
  296. internal bool FullDHMMatch(TimeSpanFormat.FormatLiterals pattern) =>
  297. _sepCount == 4
  298. && _numCount == 3
  299. && _literals0.EqualsOrdinal(pattern.Start)
  300. && _literals1.EqualsOrdinal(pattern.DayHourSep)
  301. && _literals2.EqualsOrdinal(pattern.HourMinuteSep)
  302. && _literals3.EqualsOrdinal(pattern.End);
  303. /// <summary>HMS (no days or fraction)</summary>
  304. internal bool FullHMSMatch(TimeSpanFormat.FormatLiterals pattern) =>
  305. _sepCount == 4
  306. && _numCount == 3
  307. && _literals0.EqualsOrdinal(pattern.Start)
  308. && _literals1.EqualsOrdinal(pattern.HourMinuteSep)
  309. && _literals2.EqualsOrdinal(pattern.MinuteSecondSep)
  310. && _literals3.EqualsOrdinal(pattern.End);
  311. /// <summary>DHMS (no fraction)</summary>
  312. internal bool FullDHMSMatch(TimeSpanFormat.FormatLiterals pattern) =>
  313. _sepCount == 5
  314. && _numCount == 4
  315. && _literals0.EqualsOrdinal(pattern.Start)
  316. && _literals1.EqualsOrdinal(pattern.DayHourSep)
  317. && _literals2.EqualsOrdinal(pattern.HourMinuteSep)
  318. && _literals3.EqualsOrdinal(pattern.MinuteSecondSep)
  319. && _literals4.EqualsOrdinal(pattern.End);
  320. /// <summary>HMSF (no days)</summary>
  321. internal bool FullHMSFMatch(TimeSpanFormat.FormatLiterals pattern) =>
  322. _sepCount == 5
  323. && _numCount == 4
  324. && _literals0.EqualsOrdinal(pattern.Start)
  325. && _literals1.EqualsOrdinal(pattern.HourMinuteSep)
  326. && _literals2.EqualsOrdinal(pattern.MinuteSecondSep)
  327. && _literals3.EqualsOrdinal(pattern.SecondFractionSep)
  328. && _literals4.EqualsOrdinal(pattern.End);
  329. internal TTT _lastSeenTTT;
  330. internal int _tokenCount;
  331. internal int _sepCount;
  332. internal int _numCount;
  333. private TimeSpanFormat.FormatLiterals _posLoc;
  334. private TimeSpanFormat.FormatLiterals _negLoc;
  335. private bool _posLocInit;
  336. private bool _negLocInit;
  337. private string _fullPosPattern;
  338. private string _fullNegPattern;
  339. private const int MaxTokens = 11;
  340. private const int MaxLiteralTokens = 6;
  341. private const int MaxNumericTokens = 5;
  342. internal TimeSpanToken _numbers0, _numbers1, _numbers2, _numbers3, _numbers4; // MaxNumbericTokens = 5
  343. internal ReadOnlySpan<char> _literals0, _literals1, _literals2, _literals3, _literals4, _literals5; // MaxLiteralTokens=6
  344. internal void Init(DateTimeFormatInfo dtfi)
  345. {
  346. Debug.Assert(dtfi != null);
  347. _lastSeenTTT = TTT.None;
  348. _tokenCount = 0;
  349. _sepCount = 0;
  350. _numCount = 0;
  351. _fullPosPattern = dtfi.FullTimeSpanPositivePattern;
  352. _fullNegPattern = dtfi.FullTimeSpanNegativePattern;
  353. _posLocInit = false;
  354. _negLocInit = false;
  355. }
  356. internal bool ProcessToken(ref TimeSpanToken tok, ref TimeSpanResult result)
  357. {
  358. switch (tok._ttt)
  359. {
  360. case TTT.Num:
  361. if ((_tokenCount == 0 && !AddSep(default, ref result)) || !AddNum(tok, ref result))
  362. {
  363. return false;
  364. }
  365. break;
  366. case TTT.Sep:
  367. if (!AddSep(tok._sep, ref result))
  368. {
  369. return false;
  370. }
  371. break;
  372. case TTT.NumOverflow:
  373. return result.SetOverflowFailure();
  374. default:
  375. // Some unknown token or a repeat token type in the input
  376. return result.SetBadTimeSpanFailure();
  377. }
  378. _lastSeenTTT = tok._ttt;
  379. Debug.Assert(_tokenCount == (_sepCount + _numCount), "tokenCount == (SepCount + NumCount)");
  380. return true;
  381. }
  382. private bool AddSep(ReadOnlySpan<char> sep, ref TimeSpanResult result)
  383. {
  384. if (_sepCount >= MaxLiteralTokens || _tokenCount >= MaxTokens)
  385. {
  386. return result.SetBadTimeSpanFailure();
  387. }
  388. switch (_sepCount++)
  389. {
  390. case 0: _literals0 = sep; break;
  391. case 1: _literals1 = sep; break;
  392. case 2: _literals2 = sep; break;
  393. case 3: _literals3 = sep; break;
  394. case 4: _literals4 = sep; break;
  395. default: _literals5 = sep; break;
  396. }
  397. _tokenCount++;
  398. return true;
  399. }
  400. private bool AddNum(TimeSpanToken num, ref TimeSpanResult result)
  401. {
  402. if (_numCount >= MaxNumericTokens || _tokenCount >= MaxTokens)
  403. {
  404. return result.SetBadTimeSpanFailure();
  405. }
  406. switch (_numCount++)
  407. {
  408. case 0: _numbers0 = num; break;
  409. case 1: _numbers1 = num; break;
  410. case 2: _numbers2 = num; break;
  411. case 3: _numbers3 = num; break;
  412. default: _numbers4 = num; break;
  413. }
  414. _tokenCount++;
  415. return true;
  416. }
  417. }
  418. /// <summary>Store the result of the parsing.</summary>
  419. private ref struct TimeSpanResult
  420. {
  421. internal TimeSpan parsedTimeSpan;
  422. private readonly bool _throwOnFailure;
  423. private readonly ReadOnlySpan<char> _originalTimeSpanString;
  424. internal TimeSpanResult(bool throwOnFailure, ReadOnlySpan<char> originalTimeSpanString)
  425. {
  426. parsedTimeSpan = default;
  427. _throwOnFailure = throwOnFailure;
  428. _originalTimeSpanString = originalTimeSpanString;
  429. }
  430. internal bool SetNoFormatSpecifierFailure()
  431. {
  432. if (!_throwOnFailure)
  433. {
  434. return false;
  435. }
  436. throw new FormatException(SR.Format_NoFormatSpecifier);
  437. }
  438. internal bool SetBadQuoteFailure(char failingCharacter)
  439. {
  440. if (!_throwOnFailure)
  441. {
  442. return false;
  443. }
  444. throw new FormatException(SR.Format(SR.Format_BadQuote, failingCharacter));
  445. }
  446. internal bool SetInvalidStringFailure()
  447. {
  448. if (!_throwOnFailure)
  449. {
  450. return false;
  451. }
  452. throw new FormatException(SR.Format_InvalidString);
  453. }
  454. internal bool SetArgumentNullFailure(string argumentName)
  455. {
  456. if (!_throwOnFailure)
  457. {
  458. return false;
  459. }
  460. Debug.Assert(argumentName != null);
  461. throw new ArgumentNullException(argumentName, SR.ArgumentNull_String);
  462. }
  463. internal bool SetOverflowFailure()
  464. {
  465. if (!_throwOnFailure)
  466. {
  467. return false;
  468. }
  469. throw new OverflowException(SR.Format(SR.Overflow_TimeSpanElementTooLarge, new string(_originalTimeSpanString)));
  470. }
  471. internal bool SetBadTimeSpanFailure()
  472. {
  473. if (!_throwOnFailure)
  474. {
  475. return false;
  476. }
  477. throw new FormatException(SR.Format(SR.Format_BadTimeSpan, new string(_originalTimeSpanString)));
  478. }
  479. internal bool SetBadFormatSpecifierFailure(char? formatSpecifierCharacter = null)
  480. {
  481. if (!_throwOnFailure)
  482. {
  483. return false;
  484. }
  485. throw new FormatException(SR.Format(SR.Format_BadFormatSpecifier, formatSpecifierCharacter));
  486. }
  487. }
  488. internal static long Pow10(int pow)
  489. {
  490. switch (pow)
  491. {
  492. case 0: return 1;
  493. case 1: return 10;
  494. case 2: return 100;
  495. case 3: return 1000;
  496. case 4: return 10000;
  497. case 5: return 100000;
  498. case 6: return 1000000;
  499. case 7: return 10000000;
  500. default: return (long)Math.Pow(10, pow);
  501. }
  502. }
  503. private static bool TryTimeToTicks(bool positive, TimeSpanToken days, TimeSpanToken hours, TimeSpanToken minutes, TimeSpanToken seconds, TimeSpanToken fraction, out long result)
  504. {
  505. if (days._num > MaxDays ||
  506. hours._num > MaxHours ||
  507. minutes._num > MaxMinutes ||
  508. seconds._num > MaxSeconds ||
  509. !fraction.NormalizeAndValidateFraction())
  510. {
  511. result = 0;
  512. return false;
  513. }
  514. long ticks = ((long)days._num * 3600 * 24 + (long)hours._num * 3600 + (long)minutes._num * 60 + seconds._num) * 1000;
  515. if (ticks > InternalGlobalizationHelper.MaxMilliSeconds || ticks < InternalGlobalizationHelper.MinMilliSeconds)
  516. {
  517. result = 0;
  518. return false;
  519. }
  520. result = ticks * TimeSpan.TicksPerMillisecond + fraction._num;
  521. if (positive && result < 0)
  522. {
  523. result = 0;
  524. return false;
  525. }
  526. return true;
  527. }
  528. internal static TimeSpan Parse(ReadOnlySpan<char> input, IFormatProvider formatProvider)
  529. {
  530. var parseResult = new TimeSpanResult(throwOnFailure: true, originalTimeSpanString: input);
  531. bool success = TryParseTimeSpan(input, TimeSpanStandardStyles.Any, formatProvider, ref parseResult);
  532. Debug.Assert(success, "Should have thrown on failure");
  533. return parseResult.parsedTimeSpan;
  534. }
  535. internal static bool TryParse(ReadOnlySpan<char> input, IFormatProvider formatProvider, out TimeSpan result)
  536. {
  537. var parseResult = new TimeSpanResult(throwOnFailure: false, originalTimeSpanString: input);
  538. if (TryParseTimeSpan(input, TimeSpanStandardStyles.Any, formatProvider, ref parseResult))
  539. {
  540. result = parseResult.parsedTimeSpan;
  541. return true;
  542. }
  543. result = default;
  544. return false;
  545. }
  546. internal static TimeSpan ParseExact(ReadOnlySpan<char> input, ReadOnlySpan<char> format, IFormatProvider formatProvider, TimeSpanStyles styles)
  547. {
  548. var parseResult = new TimeSpanResult(throwOnFailure: true, originalTimeSpanString: input);
  549. bool success = TryParseExactTimeSpan(input, format, formatProvider, styles, ref parseResult);
  550. Debug.Assert(success, "Should have thrown on failure");
  551. return parseResult.parsedTimeSpan;
  552. }
  553. internal static bool TryParseExact(ReadOnlySpan<char> input, ReadOnlySpan<char> format, IFormatProvider formatProvider, TimeSpanStyles styles, out TimeSpan result)
  554. {
  555. var parseResult = new TimeSpanResult(throwOnFailure: false, originalTimeSpanString: input);
  556. if (TryParseExactTimeSpan(input, format, formatProvider, styles, ref parseResult))
  557. {
  558. result = parseResult.parsedTimeSpan;
  559. return true;
  560. }
  561. result = default;
  562. return false;
  563. }
  564. internal static TimeSpan ParseExactMultiple(ReadOnlySpan<char> input, string[] formats, IFormatProvider formatProvider, TimeSpanStyles styles)
  565. {
  566. var parseResult = new TimeSpanResult(throwOnFailure: true, originalTimeSpanString: input);
  567. bool success = TryParseExactMultipleTimeSpan(input, formats, formatProvider, styles, ref parseResult);
  568. Debug.Assert(success, "Should have thrown on failure");
  569. return parseResult.parsedTimeSpan;
  570. }
  571. internal static bool TryParseExactMultiple(ReadOnlySpan<char> input, string[] formats, IFormatProvider formatProvider, TimeSpanStyles styles, out TimeSpan result)
  572. {
  573. var parseResult = new TimeSpanResult(throwOnFailure: false, originalTimeSpanString: input);
  574. if (TryParseExactMultipleTimeSpan(input, formats, formatProvider, styles, ref parseResult))
  575. {
  576. result = parseResult.parsedTimeSpan;
  577. return true;
  578. }
  579. result = default;
  580. return false;
  581. }
  582. /// <summary>Common private Parse method called by both Parse and TryParse.</summary>
  583. private static bool TryParseTimeSpan(ReadOnlySpan<char> input, TimeSpanStandardStyles style, IFormatProvider formatProvider, ref TimeSpanResult result)
  584. {
  585. input = input.Trim();
  586. if (input.IsEmpty)
  587. {
  588. return result.SetBadTimeSpanFailure();
  589. }
  590. var tokenizer = new TimeSpanTokenizer(input);
  591. var raw = new TimeSpanRawInfo();
  592. raw.Init(DateTimeFormatInfo.GetInstance(formatProvider));
  593. TimeSpanToken tok = tokenizer.GetNextToken();
  594. // The following loop will break out when we reach the end of the str or
  595. // when we can determine that the input is invalid.
  596. while (tok._ttt != TTT.End)
  597. {
  598. if (!raw.ProcessToken(ref tok, ref result))
  599. {
  600. return result.SetBadTimeSpanFailure();
  601. }
  602. tok = tokenizer.GetNextToken();
  603. }
  604. Debug.Assert(tokenizer.EOL);
  605. if (!ProcessTerminalState(ref raw, style, ref result))
  606. {
  607. return result.SetBadTimeSpanFailure();
  608. }
  609. return true;
  610. }
  611. /// <summary>
  612. /// Validate the terminal state of a standard format parse.
  613. /// Sets result.parsedTimeSpan on success.
  614. /// Calculates the resultant TimeSpan from the TimeSpanRawInfo.
  615. /// </summary>
  616. /// <remarks>
  617. /// try => +InvariantPattern, -InvariantPattern, +LocalizedPattern, -LocalizedPattern
  618. /// 1) Verify Start matches
  619. /// 2) Verify End matches
  620. /// 3) 1 number => d
  621. /// 2 numbers => h:m
  622. /// 3 numbers => h:m:s | d.h:m | h:m:.f
  623. /// 4 numbers => h:m:s.f | d.h:m:s | d.h:m:.f
  624. /// 5 numbers => d.h:m:s.f
  625. /// </remarks>
  626. private static bool ProcessTerminalState(ref TimeSpanRawInfo raw, TimeSpanStandardStyles style, ref TimeSpanResult result)
  627. {
  628. if (raw._lastSeenTTT == TTT.Num)
  629. {
  630. TimeSpanToken tok = new TimeSpanToken();
  631. tok._ttt = TTT.Sep;
  632. if (!raw.ProcessToken(ref tok, ref result))
  633. {
  634. return result.SetBadTimeSpanFailure();
  635. }
  636. }
  637. switch (raw._numCount)
  638. {
  639. case 1: return ProcessTerminal_D(ref raw, style, ref result);
  640. case 2: return ProcessTerminal_HM(ref raw, style, ref result);
  641. case 3: return ProcessTerminal_HM_S_D(ref raw, style, ref result);
  642. case 4: return ProcessTerminal_HMS_F_D(ref raw, style, ref result);
  643. case 5: return ProcessTerminal_DHMSF(ref raw, style, ref result);
  644. default: return result.SetBadTimeSpanFailure();
  645. }
  646. }
  647. /// <summary>Validate the 5-number "Days.Hours:Minutes:Seconds.Fraction" terminal case.</summary>
  648. private static bool ProcessTerminal_DHMSF(ref TimeSpanRawInfo raw, TimeSpanStandardStyles style, ref TimeSpanResult result)
  649. {
  650. if (raw._sepCount != 6)
  651. {
  652. return result.SetBadTimeSpanFailure();
  653. }
  654. Debug.Assert(raw._numCount == 5);
  655. bool inv = (style & TimeSpanStandardStyles.Invariant) != 0;
  656. bool loc = (style & TimeSpanStandardStyles.Localized) != 0;
  657. bool positive = false;
  658. bool match = false;
  659. if (inv)
  660. {
  661. if (raw.FullMatch(raw.PositiveInvariant))
  662. {
  663. match = true;
  664. positive = true;
  665. }
  666. if (!match && raw.FullMatch(raw.NegativeInvariant))
  667. {
  668. match = true;
  669. positive = false;
  670. }
  671. }
  672. if (loc)
  673. {
  674. if (!match && raw.FullMatch(raw.PositiveLocalized))
  675. {
  676. match = true;
  677. positive = true;
  678. }
  679. if (!match && raw.FullMatch(raw.NegativeLocalized))
  680. {
  681. match = true;
  682. positive = false;
  683. }
  684. }
  685. if (match)
  686. {
  687. long ticks;
  688. if (!TryTimeToTicks(positive, raw._numbers0, raw._numbers1, raw._numbers2, raw._numbers3, raw._numbers4, out ticks))
  689. {
  690. return result.SetOverflowFailure();
  691. }
  692. if (!positive)
  693. {
  694. ticks = -ticks;
  695. if (ticks > 0)
  696. {
  697. return result.SetOverflowFailure();
  698. }
  699. }
  700. result.parsedTimeSpan = new TimeSpan(ticks);
  701. return true;
  702. }
  703. return result.SetBadTimeSpanFailure();
  704. }
  705. /// <summary>
  706. /// Validate the ambiguous 4-number "Hours:Minutes:Seconds.Fraction", "Days.Hours:Minutes:Seconds",
  707. /// or "Days.Hours:Minutes:.Fraction" terminal case.
  708. /// </summary>
  709. private static bool ProcessTerminal_HMS_F_D(ref TimeSpanRawInfo raw, TimeSpanStandardStyles style, ref TimeSpanResult result)
  710. {
  711. if (raw._sepCount != 5 || (style & TimeSpanStandardStyles.RequireFull) != 0)
  712. {
  713. return result.SetBadTimeSpanFailure();
  714. }
  715. Debug.Assert(raw._numCount == 4);
  716. bool inv = ((style & TimeSpanStandardStyles.Invariant) != 0);
  717. bool loc = ((style & TimeSpanStandardStyles.Localized) != 0);
  718. long ticks = 0;
  719. bool positive = false, match = false, overflow = false;
  720. var zero = new TimeSpanToken(0);
  721. if (inv)
  722. {
  723. if (raw.FullHMSFMatch(raw.PositiveInvariant))
  724. {
  725. positive = true;
  726. match = TryTimeToTicks(positive, zero, raw._numbers0, raw._numbers1, raw._numbers2, raw._numbers3, out ticks);
  727. overflow = overflow || !match;
  728. }
  729. if (!match && raw.FullDHMSMatch(raw.PositiveInvariant))
  730. {
  731. positive = true;
  732. match = TryTimeToTicks(positive, raw._numbers0, raw._numbers1, raw._numbers2, raw._numbers3, zero, out ticks);
  733. overflow = overflow || !match;
  734. }
  735. if (!match && raw.FullAppCompatMatch(raw.PositiveInvariant))
  736. {
  737. positive = true;
  738. match = TryTimeToTicks(positive, raw._numbers0, raw._numbers1, raw._numbers2, zero, raw._numbers3, out ticks);
  739. overflow = overflow || !match;
  740. }
  741. if (!match && raw.FullHMSFMatch(raw.NegativeInvariant))
  742. {
  743. positive = false;
  744. match = TryTimeToTicks(positive, zero, raw._numbers0, raw._numbers1, raw._numbers2, raw._numbers3, out ticks);
  745. overflow = overflow || !match;
  746. }
  747. if (!match && raw.FullDHMSMatch(raw.NegativeInvariant))
  748. {
  749. positive = false;
  750. match = TryTimeToTicks(positive, raw._numbers0, raw._numbers1, raw._numbers2, raw._numbers3, zero, out ticks);
  751. overflow = overflow || !match;
  752. }
  753. if (!match && raw.FullAppCompatMatch(raw.NegativeInvariant))
  754. {
  755. positive = false;
  756. match = TryTimeToTicks(positive, raw._numbers0, raw._numbers1, raw._numbers2, zero, raw._numbers3, out ticks);
  757. overflow = overflow || !match;
  758. }
  759. }
  760. if (loc)
  761. {
  762. if (!match && raw.FullHMSFMatch(raw.PositiveLocalized))
  763. {
  764. positive = true;
  765. match = TryTimeToTicks(positive, zero, raw._numbers0, raw._numbers1, raw._numbers2, raw._numbers3, out ticks);
  766. overflow = overflow || !match;
  767. }
  768. if (!match && raw.FullDHMSMatch(raw.PositiveLocalized))
  769. {
  770. positive = true;
  771. match = TryTimeToTicks(positive, raw._numbers0, raw._numbers1, raw._numbers2, raw._numbers3, zero, out ticks);
  772. overflow = overflow || !match;
  773. }
  774. if (!match && raw.FullAppCompatMatch(raw.PositiveLocalized))
  775. {
  776. positive = true;
  777. match = TryTimeToTicks(positive, raw._numbers0, raw._numbers1, raw._numbers2, zero, raw._numbers3, out ticks);
  778. overflow = overflow || !match;
  779. }
  780. if (!match && raw.FullHMSFMatch(raw.NegativeLocalized))
  781. {
  782. positive = false;
  783. match = TryTimeToTicks(positive, zero, raw._numbers0, raw._numbers1, raw._numbers2, raw._numbers3, out ticks);
  784. overflow = overflow || !match;
  785. }
  786. if (!match && raw.FullDHMSMatch(raw.NegativeLocalized))
  787. {
  788. positive = false;
  789. match = TryTimeToTicks(positive, raw._numbers0, raw._numbers1, raw._numbers2, raw._numbers3, zero, out ticks);
  790. overflow = overflow || !match;
  791. }
  792. if (!match && raw.FullAppCompatMatch(raw.NegativeLocalized))
  793. {
  794. positive = false;
  795. match = TryTimeToTicks(positive, raw._numbers0, raw._numbers1, raw._numbers2, zero, raw._numbers3, out ticks);
  796. overflow = overflow || !match;
  797. }
  798. }
  799. if (match)
  800. {
  801. if (!positive)
  802. {
  803. ticks = -ticks;
  804. if (ticks > 0)
  805. {
  806. return result.SetOverflowFailure();
  807. }
  808. }
  809. result.parsedTimeSpan = new TimeSpan(ticks);
  810. return true;
  811. }
  812. return overflow ?
  813. result.SetOverflowFailure() : // we found at least one literal pattern match but the numbers just didn't fit
  814. result.SetBadTimeSpanFailure(); // we couldn't find a thing
  815. }
  816. /// <summary>Validate the ambiguous 3-number "Hours:Minutes:Seconds", "Days.Hours:Minutes", or "Hours:Minutes:.Fraction" terminal case.</summary>
  817. private static bool ProcessTerminal_HM_S_D(ref TimeSpanRawInfo raw, TimeSpanStandardStyles style, ref TimeSpanResult result)
  818. {
  819. if (raw._sepCount != 4 || (style & TimeSpanStandardStyles.RequireFull) != 0)
  820. {
  821. return result.SetBadTimeSpanFailure();
  822. }
  823. Debug.Assert(raw._numCount == 3);
  824. bool inv = ((style & TimeSpanStandardStyles.Invariant) != 0);
  825. bool loc = ((style & TimeSpanStandardStyles.Localized) != 0);
  826. bool positive = false, match = false, overflow = false;
  827. var zero = new TimeSpanToken(0);
  828. long ticks = 0;
  829. if (inv)
  830. {
  831. if (raw.FullHMSMatch(raw.PositiveInvariant))
  832. {
  833. positive = true;
  834. match = TryTimeToTicks(positive, zero, raw._numbers0, raw._numbers1, raw._numbers2, zero, out ticks);
  835. overflow = overflow || !match;
  836. }
  837. if (!match && raw.FullDHMMatch(raw.PositiveInvariant))
  838. {
  839. positive = true;
  840. match = TryTimeToTicks(positive, raw._numbers0, raw._numbers1, raw._numbers2, zero, zero, out ticks);
  841. overflow = overflow || !match;
  842. }
  843. if (!match && raw.PartialAppCompatMatch(raw.PositiveInvariant))
  844. {
  845. positive = true;
  846. match = TryTimeToTicks(positive, zero, raw._numbers0, raw._numbers1, zero, raw._numbers2, out ticks);
  847. overflow = overflow || !match;
  848. }
  849. if (!match && raw.FullHMSMatch(raw.NegativeInvariant))
  850. {
  851. positive = false;
  852. match = TryTimeToTicks(positive, zero, raw._numbers0, raw._numbers1, raw._numbers2, zero, out ticks);
  853. overflow = overflow || !match;
  854. }
  855. if (!match && raw.FullDHMMatch(raw.NegativeInvariant))
  856. {
  857. positive = false;
  858. match = TryTimeToTicks(positive, raw._numbers0, raw._numbers1, raw._numbers2, zero, zero, out ticks);
  859. overflow = overflow || !match;
  860. }
  861. if (!match && raw.PartialAppCompatMatch(raw.NegativeInvariant))
  862. {
  863. positive = false;
  864. match = TryTimeToTicks(positive, zero, raw._numbers0, raw._numbers1, zero, raw._numbers2, out ticks);
  865. overflow = overflow || !match;
  866. }
  867. }
  868. if (loc)
  869. {
  870. if (!match && raw.FullHMSMatch(raw.PositiveLocalized))
  871. {
  872. positive = true;
  873. match = TryTimeToTicks(positive, zero, raw._numbers0, raw._numbers1, raw._numbers2, zero, out ticks);
  874. overflow = overflow || !match;
  875. }
  876. if (!match && raw.FullDHMMatch(raw.PositiveLocalized))
  877. {
  878. positive = true;
  879. match = TryTimeToTicks(positive, raw._numbers0, raw._numbers1, raw._numbers2, zero, zero, out ticks);
  880. overflow = overflow || !match;
  881. }
  882. if (!match && raw.PartialAppCompatMatch(raw.PositiveLocalized))
  883. {
  884. positive = true;
  885. match = TryTimeToTicks(positive, zero, raw._numbers0, raw._numbers1, zero, raw._numbers2, out ticks);
  886. overflow = overflow || !match;
  887. }
  888. if (!match && raw.FullHMSMatch(raw.NegativeLocalized))
  889. {
  890. positive = false;
  891. match = TryTimeToTicks(positive, zero, raw._numbers0, raw._numbers1, raw._numbers2, zero, out ticks);
  892. overflow = overflow || !match;
  893. }
  894. if (!match && raw.FullDHMMatch(raw.NegativeLocalized))
  895. {
  896. positive = false;
  897. match = TryTimeToTicks(positive, raw._numbers0, raw._numbers1, raw._numbers2, zero, zero, out ticks);
  898. overflow = overflow || !match;
  899. }
  900. if (!match && raw.PartialAppCompatMatch(raw.NegativeLocalized))
  901. {
  902. positive = false;
  903. match = TryTimeToTicks(positive, zero, raw._numbers0, raw._numbers1, zero, raw._numbers2, out ticks);
  904. overflow = overflow || !match;
  905. }
  906. }
  907. if (match)
  908. {
  909. if (!positive)
  910. {
  911. ticks = -ticks;
  912. if (ticks > 0)
  913. {
  914. return result.SetOverflowFailure();
  915. }
  916. }
  917. result.parsedTimeSpan = new TimeSpan(ticks);
  918. return true;
  919. }
  920. return overflow ?
  921. result.SetOverflowFailure() : // we found at least one literal pattern match but the numbers just didn't fit
  922. result.SetBadTimeSpanFailure(); // we couldn't find a thing
  923. }
  924. /// <summary>Validate the 2-number "Hours:Minutes" terminal case.</summary>
  925. private static bool ProcessTerminal_HM(ref TimeSpanRawInfo raw, TimeSpanStandardStyles style, ref TimeSpanResult result)
  926. {
  927. if (raw._sepCount != 3 || (style & TimeSpanStandardStyles.RequireFull) != 0)
  928. {
  929. return result.SetBadTimeSpanFailure();
  930. }
  931. Debug.Assert(raw._numCount == 2);
  932. bool inv = ((style & TimeSpanStandardStyles.Invariant) != 0);
  933. bool loc = ((style & TimeSpanStandardStyles.Localized) != 0);
  934. bool positive = false, match = false;
  935. if (inv)
  936. {
  937. if (raw.FullHMMatch(raw.PositiveInvariant))
  938. {
  939. match = true;
  940. positive = true;
  941. }
  942. if (!match && raw.FullHMMatch(raw.NegativeInvariant))
  943. {
  944. match = true;
  945. positive = false;
  946. }
  947. }
  948. if (loc)
  949. {
  950. if (!match && raw.FullHMMatch(raw.PositiveLocalized))
  951. {
  952. match = true;
  953. positive = true;
  954. }
  955. if (!match && raw.FullHMMatch(raw.NegativeLocalized))
  956. {
  957. match = true;
  958. positive = false;
  959. }
  960. }
  961. if (match)
  962. {
  963. long ticks = 0;
  964. var zero = new TimeSpanToken(0);
  965. if (!TryTimeToTicks(positive, zero, raw._numbers0, raw._numbers1, zero, zero, out ticks))
  966. {
  967. return result.SetOverflowFailure();
  968. }
  969. if (!positive)
  970. {
  971. ticks = -ticks;
  972. if (ticks > 0)
  973. {
  974. return result.SetOverflowFailure();
  975. }
  976. }
  977. result.parsedTimeSpan = new TimeSpan(ticks);
  978. return true;
  979. }
  980. return result.SetBadTimeSpanFailure();
  981. }
  982. /// <summary>Validate the 1-number "Days" terminal case.</summary>
  983. private static bool ProcessTerminal_D(ref TimeSpanRawInfo raw, TimeSpanStandardStyles style, ref TimeSpanResult result)
  984. {
  985. if (raw._sepCount != 2 || (style & TimeSpanStandardStyles.RequireFull) != 0)
  986. {
  987. return result.SetBadTimeSpanFailure();
  988. }
  989. Debug.Assert(raw._numCount == 1);
  990. bool inv = ((style & TimeSpanStandardStyles.Invariant) != 0);
  991. bool loc = ((style & TimeSpanStandardStyles.Localized) != 0);
  992. bool positive = false, match = false;
  993. if (inv)
  994. {
  995. if (raw.FullDMatch(raw.PositiveInvariant))
  996. {
  997. match = true;
  998. positive = true;
  999. }
  1000. if (!match && raw.FullDMatch(raw.NegativeInvariant))
  1001. {
  1002. match = true;
  1003. positive = false;
  1004. }
  1005. }
  1006. if (loc)
  1007. {
  1008. if (!match && raw.FullDMatch(raw.PositiveLocalized))
  1009. {
  1010. match = true;
  1011. positive = true;
  1012. }
  1013. if (!match && raw.FullDMatch(raw.NegativeLocalized))
  1014. {
  1015. match = true;
  1016. positive = false;
  1017. }
  1018. }
  1019. if (match)
  1020. {
  1021. long ticks = 0;
  1022. var zero = new TimeSpanToken(0);
  1023. if (!TryTimeToTicks(positive, raw._numbers0, zero, zero, zero, zero, out ticks))
  1024. {
  1025. return result.SetOverflowFailure();
  1026. }
  1027. if (!positive)
  1028. {
  1029. ticks = -ticks;
  1030. if (ticks > 0)
  1031. {
  1032. return result.SetOverflowFailure();
  1033. }
  1034. }
  1035. result.parsedTimeSpan = new TimeSpan(ticks);
  1036. return true;
  1037. }
  1038. return result.SetBadTimeSpanFailure();
  1039. }
  1040. /// <summary>Common private ParseExact method called by both ParseExact and TryParseExact.</summary>
  1041. private static bool TryParseExactTimeSpan(ReadOnlySpan<char> input, ReadOnlySpan<char> format, IFormatProvider formatProvider, TimeSpanStyles styles, ref TimeSpanResult result)
  1042. {
  1043. if (format.Length == 0)
  1044. {
  1045. return result.SetBadFormatSpecifierFailure();
  1046. }
  1047. if (format.Length == 1)
  1048. {
  1049. switch (format[0])
  1050. {
  1051. case 'c':
  1052. case 't':
  1053. case 'T':
  1054. return TryParseTimeSpanConstant(input, ref result); // fast path for legacy style TimeSpan formats.
  1055. case 'g':
  1056. return TryParseTimeSpan(input, TimeSpanStandardStyles.Localized, formatProvider, ref result);
  1057. case 'G':
  1058. return TryParseTimeSpan(input, TimeSpanStandardStyles.Localized | TimeSpanStandardStyles.RequireFull, formatProvider, ref result);
  1059. default:
  1060. return result.SetBadFormatSpecifierFailure(format[0]);
  1061. }
  1062. }
  1063. return TryParseByFormat(input, format, styles, ref result);
  1064. }
  1065. /// <summary>Parse the TimeSpan instance using the specified format. Used by TryParseExactTimeSpan.</summary>
  1066. private static bool TryParseByFormat(ReadOnlySpan<char> input, ReadOnlySpan<char> format, TimeSpanStyles styles, ref TimeSpanResult result)
  1067. {
  1068. bool seenDD = false; // already processed days?
  1069. bool seenHH = false; // already processed hours?
  1070. bool seenMM = false; // already processed minutes?
  1071. bool seenSS = false; // already processed seconds?
  1072. bool seenFF = false; // already processed fraction?
  1073. int dd = 0; // parsed days
  1074. int hh = 0; // parsed hours
  1075. int mm = 0; // parsed minutes
  1076. int ss = 0; // parsed seconds
  1077. int leadingZeroes = 0; // number of leading zeroes in the parsed fraction
  1078. int ff = 0; // parsed fraction
  1079. int i = 0; // format string position
  1080. int tokenLen = 0; // length of current format token, used to update index 'i'
  1081. var tokenizer = new TimeSpanTokenizer(input, -1);
  1082. while (i < format.Length)
  1083. {
  1084. char ch = format[i];
  1085. int nextFormatChar;
  1086. switch (ch)
  1087. {
  1088. case 'h':
  1089. tokenLen = DateTimeFormat.ParseRepeatPattern(format, i, ch);
  1090. if (tokenLen > 2 || seenHH || !ParseExactDigits(ref tokenizer, tokenLen, out hh))
  1091. {
  1092. return result.SetInvalidStringFailure();
  1093. }
  1094. seenHH = true;
  1095. break;
  1096. case 'm':
  1097. tokenLen = DateTimeFormat.ParseRepeatPattern(format, i, ch);
  1098. if (tokenLen > 2 || seenMM || !ParseExactDigits(ref tokenizer, tokenLen, out mm))
  1099. {
  1100. return result.SetInvalidStringFailure();
  1101. }
  1102. seenMM = true;
  1103. break;
  1104. case 's':
  1105. tokenLen = DateTimeFormat.ParseRepeatPattern(format, i, ch);
  1106. if (tokenLen > 2 || seenSS || !ParseExactDigits(ref tokenizer, tokenLen, out ss))
  1107. {
  1108. return result.SetInvalidStringFailure();
  1109. }
  1110. seenSS = true;
  1111. break;
  1112. case 'f':
  1113. tokenLen = DateTimeFormat.ParseRepeatPattern(format, i, ch);
  1114. if (tokenLen > DateTimeFormat.MaxSecondsFractionDigits || seenFF || !ParseExactDigits(ref tokenizer, tokenLen, tokenLen, out leadingZeroes, out ff))
  1115. {
  1116. return result.SetInvalidStringFailure();
  1117. }
  1118. seenFF = true;
  1119. break;
  1120. case 'F':
  1121. tokenLen = DateTimeFormat.ParseRepeatPattern(format, i, ch);
  1122. if (tokenLen > DateTimeFormat.MaxSecondsFractionDigits || seenFF)
  1123. {
  1124. return result.SetInvalidStringFailure();
  1125. }
  1126. ParseExactDigits(ref tokenizer, tokenLen, tokenLen, out leadingZeroes, out ff);
  1127. seenFF = true;
  1128. break;
  1129. case 'd':
  1130. tokenLen = DateTimeFormat.ParseRepeatPattern(format, i, ch);
  1131. int tmp = 0;
  1132. if (tokenLen > 8 || seenDD || !ParseExactDigits(ref tokenizer, (tokenLen < 2) ? 1 : tokenLen, (tokenLen < 2) ? 8 : tokenLen, out tmp, out dd))
  1133. {
  1134. return result.SetInvalidStringFailure();
  1135. }
  1136. seenDD = true;
  1137. break;
  1138. case '\'':
  1139. case '\"':
  1140. StringBuilder enquotedString = StringBuilderCache.Acquire();
  1141. if (!DateTimeParse.TryParseQuoteString(format, i, enquotedString, out tokenLen))
  1142. {
  1143. StringBuilderCache.Release(enquotedString);
  1144. return result.SetBadQuoteFailure(ch);
  1145. }
  1146. if (!ParseExactLiteral(ref tokenizer, enquotedString))
  1147. {
  1148. StringBuilderCache.Release(enquotedString);
  1149. return result.SetInvalidStringFailure();
  1150. }
  1151. StringBuilderCache.Release(enquotedString);
  1152. break;
  1153. case '%':
  1154. // Optional format character.
  1155. // For example, format string "%d" will print day
  1156. // Most of the cases, "%" can be ignored.
  1157. nextFormatChar = DateTimeFormat.ParseNextChar(format, i);
  1158. // nextFormatChar will be -1 if we already reach the end of the format string.
  1159. // Besides, we will not allow "%%" appear in the pattern.
  1160. if (nextFormatChar >= 0 && nextFormatChar != '%')
  1161. {
  1162. tokenLen = 1; // skip the '%' and process the format character
  1163. break;
  1164. }
  1165. else
  1166. {
  1167. // This means that '%' is at the end of the format string or
  1168. // "%%" appears in the format string.
  1169. return result.SetInvalidStringFailure();
  1170. }
  1171. case '\\':
  1172. // Escaped character. Can be used to insert character into the format string.
  1173. // For example, "\d" will insert the character 'd' into the string.
  1174. //
  1175. nextFormatChar = DateTimeFormat.ParseNextChar(format, i);
  1176. if (nextFormatChar >= 0 && tokenizer.NextChar == (char)nextFormatChar)
  1177. {
  1178. tokenLen = 2;
  1179. }
  1180. else
  1181. {
  1182. // This means that '\' is at the end of the format string or the literal match failed.
  1183. return result.SetInvalidStringFailure();
  1184. }
  1185. break;
  1186. default:
  1187. return result.SetInvalidStringFailure();
  1188. }
  1189. i += tokenLen;
  1190. }
  1191. if (!tokenizer.EOL)
  1192. {
  1193. // the custom format didn't consume the entire input
  1194. return result.SetBadTimeSpanFailure();
  1195. }
  1196. bool positive = (styles & TimeSpanStyles.AssumeNegative) == 0;
  1197. if (TryTimeToTicks(positive, new TimeSpanToken(dd),
  1198. new TimeSpanToken(hh),
  1199. new TimeSpanToken(mm),
  1200. new TimeSpanToken(ss),
  1201. new TimeSpanToken(ff, leadingZeroes),
  1202. out long ticks))
  1203. {
  1204. if (!positive)
  1205. {
  1206. ticks = -ticks;
  1207. }
  1208. result.parsedTimeSpan = new TimeSpan(ticks);
  1209. return true;
  1210. }
  1211. else
  1212. {
  1213. return result.SetOverflowFailure();
  1214. }
  1215. }
  1216. private static bool ParseExactDigits(ref TimeSpanTokenizer tokenizer, int minDigitLength, out int result)
  1217. {
  1218. result = 0;
  1219. int zeroes = 0;
  1220. int maxDigitLength = (minDigitLength == 1) ? 2 : minDigitLength;
  1221. return ParseExactDigits(ref tokenizer, minDigitLength, maxDigitLength, out zeroes, out result);
  1222. }
  1223. private static bool ParseExactDigits(ref TimeSpanTokenizer tokenizer, int minDigitLength, int maxDigitLength, out int zeroes, out int result)
  1224. {
  1225. int tmpResult = 0, tmpZeroes = 0;
  1226. int tokenLength = 0;
  1227. while (tokenLength < maxDigitLength)
  1228. {
  1229. char ch = tokenizer.NextChar;
  1230. if (ch < '0' || ch > '9')
  1231. {
  1232. tokenizer.BackOne();
  1233. break;
  1234. }
  1235. tmpResult = tmpResult * 10 + (ch - '0');
  1236. if (tmpResult == 0) tmpZeroes++;
  1237. tokenLength++;
  1238. }
  1239. zeroes = tmpZeroes;
  1240. result = tmpResult;
  1241. return tokenLength >= minDigitLength;
  1242. }
  1243. private static bool ParseExactLiteral(ref TimeSpanTokenizer tokenizer, StringBuilder enquotedString)
  1244. {
  1245. for (int i = 0; i < enquotedString.Length; i++)
  1246. {
  1247. if (enquotedString[i] != tokenizer.NextChar)
  1248. {
  1249. return false;
  1250. }
  1251. }
  1252. return true;
  1253. }
  1254. /// <summary>
  1255. /// Parses the "c" (constant) format. This code is 100% identical to the non-globalized v1.0-v3.5 TimeSpan.Parse() routine
  1256. /// and exists for performance/appcompat with legacy callers who cannot move onto the globalized Parse overloads.
  1257. /// </summary>
  1258. private static bool TryParseTimeSpanConstant(ReadOnlySpan<char> input, ref TimeSpanResult result) =>
  1259. new StringParser().TryParse(input, ref result);
  1260. private ref struct StringParser
  1261. {
  1262. private ReadOnlySpan<char> _str;
  1263. private char _ch;
  1264. private int _pos;
  1265. private int _len;
  1266. internal void NextChar()
  1267. {
  1268. if (_pos < _len)
  1269. {
  1270. _pos++;
  1271. }
  1272. _ch = _pos < _len ?
  1273. _str[_pos] :
  1274. (char)0;
  1275. }
  1276. internal char NextNonDigit()
  1277. {
  1278. int i = _pos;
  1279. while (i < _len)
  1280. {
  1281. char ch = _str[i];
  1282. if (ch < '0' || ch > '9') return ch;
  1283. i++;
  1284. }
  1285. return (char)0;
  1286. }
  1287. internal bool TryParse(ReadOnlySpan<char> input, ref TimeSpanResult result)
  1288. {
  1289. result.parsedTimeSpan = default;
  1290. _str = input;
  1291. _len = input.Length;
  1292. _pos = -1;
  1293. NextChar();
  1294. SkipBlanks();
  1295. bool negative = false;
  1296. if (_ch == '-')
  1297. {
  1298. negative = true;
  1299. NextChar();
  1300. }
  1301. long time;
  1302. if (NextNonDigit() == ':')
  1303. {
  1304. if (!ParseTime(out time, ref result))
  1305. {
  1306. return false;
  1307. };
  1308. }
  1309. else
  1310. {
  1311. int days;
  1312. if (!ParseInt((int)(0x7FFFFFFFFFFFFFFFL / TimeSpan.TicksPerDay), out days, ref result))
  1313. {
  1314. return false;
  1315. }
  1316. time = days * TimeSpan.TicksPerDay;
  1317. if (_ch == '.')
  1318. {
  1319. NextChar();
  1320. long remainingTime;
  1321. if (!ParseTime(out remainingTime, ref result))
  1322. {
  1323. return false;
  1324. };
  1325. time += remainingTime;
  1326. }
  1327. }
  1328. if (negative)
  1329. {
  1330. time = -time;
  1331. // Allow -0 as well
  1332. if (time > 0)
  1333. {
  1334. return result.SetOverflowFailure();
  1335. }
  1336. }
  1337. else
  1338. {
  1339. if (time < 0)
  1340. {
  1341. return result.SetOverflowFailure();
  1342. }
  1343. }
  1344. SkipBlanks();
  1345. if (_pos < _len)
  1346. {
  1347. return result.SetBadTimeSpanFailure();
  1348. }
  1349. result.parsedTimeSpan = new TimeSpan(time);
  1350. return true;
  1351. }
  1352. internal bool ParseInt(int max, out int i, ref TimeSpanResult result)
  1353. {
  1354. i = 0;
  1355. int p = _pos;
  1356. while (_ch >= '0' && _ch <= '9')
  1357. {
  1358. if ((i & 0xF0000000) != 0)
  1359. {
  1360. return result.SetOverflowFailure();
  1361. }
  1362. i = i * 10 + _ch - '0';
  1363. if (i < 0)
  1364. {
  1365. return result.SetOverflowFailure();
  1366. }
  1367. NextChar();
  1368. }
  1369. if (p == _pos)
  1370. {
  1371. return result.SetBadTimeSpanFailure();
  1372. }
  1373. if (i > max)
  1374. {
  1375. return result.SetOverflowFailure();
  1376. }
  1377. return true;
  1378. }
  1379. internal bool ParseTime(out long time, ref TimeSpanResult result)
  1380. {
  1381. time = 0;
  1382. int unit;
  1383. if (!ParseInt(23, out unit, ref result))
  1384. {
  1385. return false;
  1386. }
  1387. time = unit * TimeSpan.TicksPerHour;
  1388. if (_ch != ':')
  1389. {
  1390. return result.SetBadTimeSpanFailure();
  1391. }
  1392. NextChar();
  1393. if (!ParseInt(59, out unit, ref result))
  1394. {
  1395. return false;
  1396. }
  1397. time += unit * TimeSpan.TicksPerMinute;
  1398. if (_ch == ':')
  1399. {
  1400. NextChar();
  1401. // allow seconds with the leading zero
  1402. if (_ch != '.')
  1403. {
  1404. if (!ParseInt(59, out unit, ref result))
  1405. {
  1406. return false;
  1407. }
  1408. time += unit * TimeSpan.TicksPerSecond;
  1409. }
  1410. if (_ch == '.')
  1411. {
  1412. NextChar();
  1413. int f = (int)TimeSpan.TicksPerSecond;
  1414. while (f > 1 && _ch >= '0' && _ch <= '9')
  1415. {
  1416. f /= 10;
  1417. time += (_ch - '0') * f;
  1418. NextChar();
  1419. }
  1420. }
  1421. }
  1422. return true;
  1423. }
  1424. internal void SkipBlanks()
  1425. {
  1426. while (_ch == ' ' || _ch == '\t') NextChar();
  1427. }
  1428. }
  1429. /// <summary>Common private ParseExactMultiple method called by both ParseExactMultiple and TryParseExactMultiple.</summary>
  1430. private static bool TryParseExactMultipleTimeSpan(ReadOnlySpan<char> input, string[] formats, IFormatProvider formatProvider, TimeSpanStyles styles, ref TimeSpanResult result)
  1431. {
  1432. if (formats == null)
  1433. {
  1434. return result.SetArgumentNullFailure(nameof(formats));
  1435. }
  1436. if (input.Length == 0)
  1437. {
  1438. return result.SetBadTimeSpanFailure();
  1439. }
  1440. if (formats.Length == 0)
  1441. {
  1442. return result.SetNoFormatSpecifierFailure();
  1443. }
  1444. // Do a loop through the provided formats and see if we can parse succesfully in
  1445. // one of the formats.
  1446. for (int i = 0; i < formats.Length; i++)
  1447. {
  1448. if (formats[i] == null || formats[i].Length == 0)
  1449. {
  1450. return result.SetBadFormatSpecifierFailure();
  1451. }
  1452. // Create a new non-throwing result each time to ensure the runs are independent.
  1453. TimeSpanResult innerResult = new TimeSpanResult(throwOnFailure: false, originalTimeSpanString: input);
  1454. if (TryParseExactTimeSpan(input, formats[i], formatProvider, styles, ref innerResult))
  1455. {
  1456. result.parsedTimeSpan = innerResult.parsedTimeSpan;
  1457. return true;
  1458. }
  1459. }
  1460. return result.SetBadTimeSpanFailure();
  1461. }
  1462. }
  1463. }