Skip to content

Commit 6fe6961

Browse files
committed
Include time zone information for Local and Utc DateTimeKind (#3910)
This commit adds the time zone information for a DateTime within a DateMath type, when the DateTimeKind is Local or Utc. This behaviour aligns 7.x with 6.x. Introduce StringBuilderCache for caching a StringBuilder per thread. Use of this can be extended in a later commit. Fixes #3899
1 parent a49fc63 commit 6fe6961

File tree

6 files changed

+157
-20
lines changed

6 files changed

+157
-20
lines changed

docs/common-options/date-math/date-math-expressions.asciidoc

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -83,14 +83,28 @@ anchor will be an actual `DateTime`, even after a serialization/deserialization
8383
[source,csharp]
8484
----
8585
var date = new DateTime(2015, 05, 05);
86-
Expect("2015-05-05T00:00:00")
87-
.WhenSerializing<Nest.DateMath>(date)
88-
.AssertSubject(dateMath => ((IDateMath)dateMath)
89-
.Anchor.Match(
90-
d => d.Should().Be(date),
91-
s => s.Should().BeNull()
92-
)
93-
);
86+
----
87+
88+
will serialize to
89+
90+
[source,javascript]
91+
----
92+
"2015-05-05T00:00:00"
93+
----
94+
95+
When the `DateTime` is local or UTC, the time zone information is included.
96+
For example, for a UTC `DateTime`
97+
98+
[source,csharp]
99+
----
100+
var utcDate = new DateTime(2015, 05, 05, 0, 0, 0, DateTimeKind.Utc);
101+
----
102+
103+
will serialize to
104+
105+
[source,javascript]
106+
----
107+
"2015-05-05T00:00:00Z"
94108
----
95109

96110
==== Complex expressions

src/CodeGeneration/DocGenerator/StringExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ public static string RemoveNumberOfLeadingTabsOrSpacesAfterNewline(this string i
213213

214214
public static string[] SplitOnNewLines(this string input, StringSplitOptions options) => input.Split(new[] { "\r\n", "\n" }, options);
215215

216-
public static bool TryGetJsonForAnonymousType(this string anonymousTypeString, out string json)
216+
public static bool TryGetJsonForExpressionSyntax(this string anonymousTypeString, out string json)
217217
{
218218
json = null;
219219

src/CodeGeneration/DocGenerator/SyntaxNodeExtensions.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,11 +72,10 @@ public static bool TryGetJsonForSyntaxNode(this SyntaxNode node, out string json
7272
json = null;
7373

7474
// find the first anonymous object or new object expression
75-
var creationExpressionSyntax = node.DescendantNodes()
76-
.FirstOrDefault(n => n is AnonymousObjectCreationExpressionSyntax || n is ObjectCreationExpressionSyntax);
75+
var syntax = node.DescendantNodes()
76+
.FirstOrDefault(n => n is AnonymousObjectCreationExpressionSyntax || n is ObjectCreationExpressionSyntax || n is LiteralExpressionSyntax);
7777

78-
return creationExpressionSyntax != null &&
79-
creationExpressionSyntax.ToFullString().TryGetJsonForAnonymousType(out json);
78+
return syntax != null && syntax.ToFullString().TryGetJsonForExpressionSyntax(out json);
8079
}
8180

8281
/// <summary>
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
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+
using System;
6+
using System.Text;
7+
8+
namespace Elasticsearch.Net.Extensions
9+
{
10+
/// <summary>Provide a cached reusable instance of stringbuilder per thread.</summary>
11+
internal static class StringBuilderCache
12+
{
13+
private const int DefaultCapacity = 16; // == StringBuilder.DefaultCapacity
14+
15+
// The value 360 was chosen in discussion with performance experts as a compromise between using
16+
// as little memory per thread as possible and still covering a large part of short-lived
17+
// StringBuilder creations on the startup path of VS designers.
18+
private const int MaxBuilderSize = 360;
19+
20+
// WARNING: We allow diagnostic tools to directly inspect this member (t_cachedInstance).
21+
// See https://github.com/dotnet/corert/blob/master/Documentation/design-docs/diagnostics/diagnostics-tools-contract.md for more details.
22+
// Please do not change the type, the name, or the semantic usage of this member without understanding the implication for tools.
23+
// Get in touch with the diagnostics team if you have questions.
24+
[ThreadStatic]
25+
private static StringBuilder _cachedInstance;
26+
27+
/// <summary>Get a StringBuilder for the specified capacity.</summary>
28+
/// <remarks>If a StringBuilder of an appropriate size is cached, it will be returned and the cache emptied.</remarks>
29+
public static StringBuilder Acquire(int capacity = DefaultCapacity)
30+
{
31+
if (capacity <= MaxBuilderSize)
32+
{
33+
var sb = _cachedInstance;
34+
if (sb != null)
35+
{
36+
// Avoid stringbuilder block fragmentation by getting a new StringBuilder
37+
// when the requested size is larger than the current capacity
38+
if (capacity <= sb.Capacity)
39+
{
40+
_cachedInstance = null;
41+
sb.Clear();
42+
return sb;
43+
}
44+
}
45+
}
46+
47+
return new StringBuilder(capacity);
48+
}
49+
50+
/// <summary>Place the specified builder in the cache if it is not too big.</summary>
51+
public static void Release(StringBuilder sb)
52+
{
53+
if (sb.Capacity <= MaxBuilderSize) _cachedInstance = sb;
54+
}
55+
56+
/// <summary>ToString() the stringbuilder, Release it to the cache, and return the resulting string.</summary>
57+
public static string GetStringAndRelease(StringBuilder sb)
58+
{
59+
var result = sb.ToString();
60+
Release(sb);
61+
return result;
62+
}
63+
}
64+
}

src/Nest/CommonOptions/DateMath/DateMath.cs

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Globalization;
34
using System.Text;
45
using System.Text.RegularExpressions;
56
using Elasticsearch.Net.Extensions;
@@ -109,21 +110,50 @@ public override string ToString()
109110
}
110111

111112
/// <summary>
112-
/// Formats a <see cref="DateTime"/> to have a minimum of 3 decimal places if there
113-
/// are sub second values
113+
/// Formats a <see cref="DateTime"/> to have a minimum of 3 decimal places if there are sub second values
114114
/// </summary>
115-
/// Fixes bug in Elasticsearch: https://github.com/elastic/elasticsearch/pull/41871
116115
private static string ToMinThreeDecimalPlaces(DateTime dateTime)
117116
{
118-
var format = dateTime.ToString("yyyy-MM-ddTHH:mm:ss.FFFFFFF");
117+
var builder = StringBuilderCache.Acquire(33);
118+
var format = dateTime.ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss.FFFFFFF", CultureInfo.InvariantCulture);
119+
builder.Append(format);
119120

121+
// Fixes bug in Elasticsearch: https://github.com/elastic/elasticsearch/pull/41871
120122
if (format.Length > 20 && format.Length < 23)
121123
{
122124
var diff = 23 - format.Length;
123-
return $"{format}{new string('0', diff)}";
125+
for (int i = 0; i < diff; i++)
126+
builder.Append('0');
124127
}
125128

126-
return format;
129+
switch (dateTime.Kind)
130+
{
131+
case DateTimeKind.Local:
132+
var offset = TimeZoneInfo.Local.GetUtcOffset(dateTime);
133+
if (offset >= TimeSpan.Zero)
134+
builder.Append('+');
135+
else
136+
{
137+
builder.Append('-');
138+
offset = offset.Negate();
139+
}
140+
141+
AppendTwoDigitNumber(builder, offset.Hours);
142+
builder.Append(':');
143+
AppendTwoDigitNumber(builder, offset.Minutes);
144+
break;
145+
case DateTimeKind.Utc:
146+
builder.Append('Z');
147+
break;
148+
}
149+
150+
return StringBuilderCache.GetStringAndRelease(builder);
151+
}
152+
153+
private static void AppendTwoDigitNumber(StringBuilder result, int val)
154+
{
155+
result.Append((char)('0' + (val / 10)));
156+
result.Append((char)('0' + (val % 10)));
127157
}
128158
}
129159

src/Tests/Tests/CommonOptions/DateMath/DateMathExpressions.doc.cs

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,14 +61,44 @@ [U] public void SimpleExpressions()
6161
* anchor will be an actual `DateTime`, even after a serialization/deserialization round trip
6262
*/
6363
var date = new DateTime(2015, 05, 05);
64-
Expect("2015-05-05T00:00:00")
64+
65+
/**
66+
* will serialize to
67+
*/
68+
//json
69+
var expected = "2015-05-05T00:00:00";
70+
71+
// hide
72+
Expect(expected)
6573
.WhenSerializing<Nest.DateMath>(date)
6674
.AssertSubject(dateMath => ((IDateMath)dateMath)
6775
.Anchor.Match(
6876
d => d.Should().Be(date),
6977
s => s.Should().BeNull()
7078
)
7179
);
80+
81+
/**
82+
* When the `DateTime` is local or UTC, the time zone information is included.
83+
* For example, for a UTC `DateTime`
84+
*/
85+
var utcDate = new DateTime(2015, 05, 05, 0, 0, 0, DateTimeKind.Utc);
86+
87+
/**
88+
* will serialize to
89+
*/
90+
//json
91+
expected = "2015-05-05T00:00:00Z";
92+
93+
// hide
94+
Expect(expected)
95+
.WhenSerializing<Nest.DateMath>(utcDate)
96+
.AssertSubject(dateMath => ((IDateMath)dateMath)
97+
.Anchor.Match(
98+
d => d.Should().Be(utcDate),
99+
s => s.Should().BeNull()
100+
)
101+
);
72102
}
73103

74104
[U] public void ComplexExpressions()

0 commit comments

Comments
 (0)