TimeSpanParse.cs 64 KB

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