Skip to content

Commit 3647010

Browse files
authored
make sure we can parse exponential notation and tostring reflects the… (#2779)
* make sure we can parse exponential notation and tostring reflects the exponent * fix failing tests to deal with bigger exponent from ToString() * rename mantissaformat to exponentformat * stricter regular expression for time parsing
1 parent fc29e6a commit 3647010

File tree

3 files changed

+37
-12
lines changed

3 files changed

+37
-12
lines changed

src/Nest/CommonOptions/TimeUnit/Time.cs

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.ComponentModel;
23
using System.Globalization;
34
using System.Text.RegularExpressions;
45
using Elasticsearch.Net;
@@ -20,8 +21,15 @@ public class Time : IComparable<Time>, IEquatable<Time>
2021
private const double MicrosecondsInAMillisecond = 10;
2122

2223
private static readonly Regex ExpressionRegex =
23-
new Regex(@"^(?<factor>[-+]?\d+(?:\.\d+)?)\s*(?<interval>(?:y|w|d|h|m|s|ms|nanos|micros))?$",
24-
RegexOptions.Compiled | RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase);
24+
new Regex(@"^
25+
(?<factor>[+\-]? # open factor capture, allowing optional +- signs
26+
(?:(?#numeric)(?:\d+(?:\.\d*)?)|(?:\.\d+)) #a numeric in the forms: (N, N., .N, N.N)
27+
(?:(?#exponent)e[+\-]?\d+)? #an optional exponential scientific component, E also matches here (IgnoreCase)
28+
) # numeric and exponent fall under the factor capture
29+
\s{0,10} #optional spaces (sanity checked for max 10 repetitions)
30+
(?<interval>(?:y|w|d|h|m|s|ms|nanos|micros))? #optional interval indicator
31+
$",
32+
RegexOptions.Compiled | RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
2533

2634
private static double FLOAT_TOLERANCE = 0.0000001;
2735

@@ -47,7 +55,7 @@ public static implicit operator Time(double milliseconds)
4755
public static Time MinusOne { get; } = new Time(-1, true);
4856
public static Time Zero { get; } = new Time(0, true);
4957

50-
protected Time(int specialFactor, bool specialValue)
58+
private Time(int specialFactor, bool specialValue)
5159
{
5260
if (!specialValue) throw new ArgumentException("this constructor is only for static TimeValues");
5361
this.StaticTimeValue = specialFactor;
@@ -79,8 +87,11 @@ private void ParseExpression(string timeUnit)
7987
{
8088
var match = ExpressionRegex.Match(timeUnit);
8189
if (!match.Success) throw new ArgumentException($"Time expression '{timeUnit}' string is invalid", nameof(timeUnit));
90+
var factor = match.Groups["factor"].Value;
91+
if (!double.TryParse(factor, NumberStyles.Any ,CultureInfo.InvariantCulture, out double f))
92+
throw new ArgumentException($"Time expression '{timeUnit}' contains invalid factor: {factor}", nameof(timeUnit));
8293

83-
this.Factor = double.Parse(match.Groups["factor"].Value, CultureInfo.InvariantCulture);
94+
this.Factor = f;
8495
var interval = match.Groups["interval"].Success ? match.Groups["interval"].Value : null;
8596
switch (interval)
8697
{
@@ -117,7 +128,7 @@ public int CompareTo(Time other)
117128
// ReSharper enable PossibleInvalidOperationException
118129
};
119130

120-
if (this.ApproximateMilliseconds == other.ApproximateMilliseconds) return 0;
131+
if (Math.Abs(this.ApproximateMilliseconds - other.ApproximateMilliseconds) < FLOAT_TOLERANCE) return 0;
121132
if (this.ApproximateMilliseconds < other.ApproximateMilliseconds) return -1;
122133
return 1;
123134
}
@@ -196,7 +207,9 @@ public override string ToString()
196207
return this.StaticTimeValue.Value.ToString();
197208
if (!this.Factor.HasValue)
198209
return "<bad Time object should not happen>";
199-
var factor = this.Factor.Value.ToString("0.##", CultureInfo.InvariantCulture);
210+
211+
var mantissa = ExponentFormat(this.Factor.Value);
212+
var factor = this.Factor.Value.ToString("0." + mantissa, CultureInfo.InvariantCulture);
200213
return (this.Interval.HasValue) ? factor + this.Interval.Value.GetStringValue() : factor;
201214
}
202215

@@ -307,5 +320,15 @@ private void Reduce(double ms)
307320
Interval = TimeUnit.Millisecond;
308321
}
309322
}
323+
324+
private static string ExponentFormat(double d)
325+
{
326+
// Translate the double into sign, exponent and mantissa.
327+
var bits = BitConverter.DoubleToInt64Bits(d);
328+
// Note that the shift is sign-extended, hence the test against -1 not 1
329+
var exponent = (int) ((bits >> 52) & 0x7ffL);
330+
return new string('#', Math.Max(2, exponent));
331+
}
332+
310333
}
311334
}

src/Tests/CommonOptions/TimeUnit/TimeUnits.doc.cs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,8 @@ [U]public void StringImplicitConversionParsing()
137137
{
138138
var testCases = new StringParsingTestCases
139139
{
140+
{ "2.000000000e-06ms", TimeSpan.FromMilliseconds(2.000000000e-06), "0.000002ms"},
141+
{ "3.1e-11ms", TimeSpan.FromMilliseconds(3.1e-11), "0.000000000031ms"},
140142
{ "1000 nanos", new TimeSpan(10) , "1000nanos"},
141143
{ "1000nanos", new TimeSpan(10), "1000nanos"},
142144
{ "1000 NANOS", new TimeSpan(10), "1000nanos" },
@@ -195,17 +197,17 @@ [U] public void UsingInterval()
195197
Expect("year").WhenSerializing<Union<DateInterval, Time>>(DateInterval.Year);
196198

197199
Expect("2d").WhenSerializing<Union<DateInterval, Time>>((Time)"2d");
198-
Expect("1.16w").WhenSerializing<Union<DateInterval, Time>>((Time)TimeSpan.FromDays(8.1));
200+
Expect("1.15714285714286w").WhenSerializing<Union<DateInterval, Time>>((Time)TimeSpan.FromDays(8.1));
199201
}
200202

201203
//hide
202204
[U] public void MillisecondsNeverSerializeToMonthsOrYears()
203205
{
204206
double millisecondsInAMonth = 2592000000;
205-
Expect("4.29w").WhenSerializing(new Time(millisecondsInAMonth));
206-
Expect("8.57w").WhenSerializing(new Time(millisecondsInAMonth * 2));
207-
Expect("51.43w").WhenSerializing(new Time(millisecondsInAMonth * 12));
208-
Expect("102.86w").WhenSerializing(new Time(millisecondsInAMonth * 24));
207+
Expect("4.28571428571429w").WhenSerializing(new Time(millisecondsInAMonth));
208+
Expect("8.57142857142857w").WhenSerializing(new Time(millisecondsInAMonth * 2));
209+
Expect("51.4285714285714w").WhenSerializing(new Time(millisecondsInAMonth * 12));
210+
Expect("102.857142857143w").WhenSerializing(new Time(millisecondsInAMonth * 24));
209211
}
210212

211213
//hide

src/Tests/tests.default.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
# tracked by git).
66

77
# mode either u (unit test), i (integration test) or m (mixed mode)
8-
mode: i
8+
mode: u
99
# the elasticsearch version that should be started
1010
# Can be a snapshot version of sonatype or "latest" to get the latest snapshot of sonatype
1111
elasticsearch_version: 5.4.0

0 commit comments

Comments
 (0)