Lean  $LEAN_TAG$
StreamReaderExtensions.cs
1 /*
2  * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
3  * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14 */
15 
16 using System;
17 using System.Globalization;
18 using System.IO;
19 using System.Runtime.CompilerServices;
20 using System.Text;
21 
22 namespace QuantConnect.Util
23 {
24  /// <summary>
25  /// Extension methods to fetch data from a <see cref="StreamReader"/> instance
26  /// </summary>
27  /// <remarks>The value of these methods is performance. The objective is to avoid using
28  /// <see cref="StreamReader.ReadLine"/> and having to create intermediate substrings, parsing and splitting</remarks>
29  public static class StreamReaderExtensions
30  {
31  // we use '-1' value as a flag to determine whether we have decimal places or not, so we avoid having another variable required
32  private const int NoDecimalPlaces = -1;
33  private const char NoMoreData = unchecked((char)-1);
34  private const char DefaultDelimiter = ',';
35 
36  /// <summary>
37  /// Gets a decimal from the provided stream reader
38  /// </summary>
39  /// <param name="stream">The data stream</param>
40  /// <param name="delimiter">The data delimiter character to use, default is ','</param>
41  /// <returns>The decimal read from the stream</returns>
42  [MethodImpl(MethodImplOptions.AggressiveInlining)]
43  public static decimal GetDecimal(this StreamReader stream, char delimiter = DefaultDelimiter)
44  {
45  return GetDecimal(stream, out _, delimiter);
46  }
47 
48  /// <summary>
49  /// Gets a decimal from the provided stream reader
50  /// </summary>
51  /// <param name="stream">The data stream</param>
52  /// <param name="delimiter">The data delimiter character to use, default is ','</param>
53  /// <param name="pastEndLine">True if end line was past, useful for consumers to know a line ended</param>
54  /// <returns>The decimal read from the stream</returns>
55  [MethodImpl(MethodImplOptions.AggressiveInlining)]
56  public static decimal GetDecimal(this StreamReader stream, out bool pastEndLine, char delimiter = DefaultDelimiter)
57  {
58  long value = 0;
59  var decimalPlaces = NoDecimalPlaces;
60  var current = (char)stream.Read();
61 
62  while (current == ' ')
63  {
64  current = (char)stream.Read();
65  }
66 
67  var isNegative = current == '-';
68  if (isNegative)
69  {
70  current = (char)stream.Read();
71  }
72 
73  pastEndLine = current == '\n' || current == '\r' && (stream.Peek() != '\n' || stream.Read() == '\n') || current == NoMoreData;
74  while (!(current == delimiter || pastEndLine || current == ' '))
75  {
76  if (current == '.')
77  {
78  decimalPlaces = 0;
79  }
80  else
81  {
82  value = value * 10 + (current - '0');
83  if(decimalPlaces != NoDecimalPlaces)
84  {
85  decimalPlaces++;
86  }
87  }
88  current = (char)stream.Read();
89  pastEndLine = current == '\n' || current == '\r' && (stream.Peek() != '\n' || stream.Read() == '\n') || current == NoMoreData;
90  }
91 
92  var lo = (int)value;
93  var mid = (int)(value >> 32);
94  return new decimal(lo, mid, 0, isNegative, (byte)(decimalPlaces != NoDecimalPlaces ? decimalPlaces : 0));
95  }
96 
97  /// <summary>
98  /// Gets a date time instance from a stream reader
99  /// </summary>
100  /// <param name="stream">The data stream</param>
101  /// <param name="format">The format in which the date time is</param>
102  /// <param name="delimiter">The data delimiter character to use, default is ','</param>
103  /// <returns>The date time instance read</returns>
104  [MethodImpl(MethodImplOptions.AggressiveInlining)]
105  public static DateTime GetDateTime(this StreamReader stream, string format = DateFormat.TwelveCharacter, char delimiter = DefaultDelimiter)
106  {
107  var current = (char)stream.Read();
108  while (current == ' ')
109  {
110  current = (char)stream.Read();
111  }
112 
113  var index = 0;
114  // we know the exact format we want to parse so we can allocate the char array and not use an expensive string builder
115  var data = new char[format.Length];
116  while (!(current == delimiter || current == '\n' || current == '\r' && (stream.Peek() != '\n' || stream.Read() == '\n') || current == NoMoreData))
117  {
118  data[index++] = current;
119  current = (char)stream.Read();
120  }
121 
122  return DateTime.ParseExact(data,
123  format,
124  CultureInfo.InvariantCulture);
125  }
126 
127  /// <summary>
128  /// Gets an integer from a stream reader
129  /// </summary>
130  /// <param name="stream">The data stream</param>
131  /// <param name="delimiter">The data delimiter character to use, default is ','</param>
132  /// <returns>The integer instance read</returns>
133  [MethodImpl(MethodImplOptions.AggressiveInlining)]
134  public static int GetInt32(this StreamReader stream, char delimiter = DefaultDelimiter)
135  {
136  var result = 0;
137  var current = (char)stream.Read();
138 
139  while (current == ' ')
140  {
141  current = (char)stream.Read();
142  }
143 
144  var isNegative = current == '-';
145  if (isNegative)
146  {
147  current = (char)stream.Read();
148  }
149 
150  while (!(current == delimiter || current == '\n' || current == '\r' && (stream.Peek() != '\n' || stream.Read() == '\n') || current == NoMoreData || current == ' '))
151  {
152  result = (current - '0') + result * 10;
153  current = (char)stream.Read();
154  }
155  return isNegative ? result * -1 : result;
156  }
157 
158  /// <summary>
159  /// Gets a string from a stream reader
160  /// </summary>
161  /// <param name="stream">The data stream</param>
162  /// <param name="delimiter">The data delimiter character to use, default is ','</param>
163  /// <returns>The string instance read</returns>
164  [MethodImpl(MethodImplOptions.AggressiveInlining)]
165  public static string GetString(this StreamReader stream, char delimiter = DefaultDelimiter)
166  {
167  StringBuilder result = null;
168  var current = (char)stream.Read();
169 
170  while (!(current == delimiter || current == '\n' || current == '\r' && (stream.Peek() != '\n' || stream.Read() == '\n') || current == NoMoreData))
171  {
172  if (result == null)
173  {
174  result = new StringBuilder();
175  }
176  result.Append(current);
177  current = (char)stream.Read();
178  }
179 
180  return result == null ? string.Empty : result.ToString();
181  }
182  }
183 }