Skip to content
This repository was archived by the owner on Nov 20, 2018. It is now read-only.

Commit 063d6ec

Browse files
khellangpakrym
authored andcommitted
Added custom RFC 1123 DateTimeFormatter to improve allocation profile (#716)
1 parent 874dceb commit 063d6ec

File tree

7 files changed

+161
-16
lines changed

7 files changed

+161
-16
lines changed

src/Microsoft.Net.Http.Headers/ContentDispositionHeaderValue.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,7 @@ private void SetDate(string parameter, DateTimeOffset? date)
321321
else
322322
{
323323
// Must always be quoted
324-
var dateString = string.Format(CultureInfo.InvariantCulture, "\"{0}\"", HttpRuleParser.DateToString(date.Value));
324+
var dateString = HeaderUtilities.FormatDate(date.Value, quoted: true);
325325
if (dateParameter != null)
326326
{
327327
dateParameter.Value = dateString;
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Globalization;
6+
using System.Runtime.CompilerServices;
7+
using Microsoft.Extensions.Primitives;
8+
9+
namespace Microsoft.Net.Http.Headers
10+
{
11+
internal static class DateTimeFormatter
12+
{
13+
private static readonly DateTimeFormatInfo FormatInfo = CultureInfo.InvariantCulture.DateTimeFormat;
14+
15+
private static readonly string[] MonthNames = FormatInfo.AbbreviatedMonthNames;
16+
private static readonly string[] DayNames = FormatInfo.AbbreviatedDayNames;
17+
18+
private static readonly int Rfc1123DateLength = "ddd, dd MMM yyyy HH:mm:ss GMT".Length;
19+
private static readonly int QuotedRfc1123DateLength = Rfc1123DateLength + 2;
20+
21+
// ASCII numbers are in the range 48 - 57.
22+
private const int AsciiNumberOffset = 0x30;
23+
24+
private const string Gmt = "GMT";
25+
private const char Comma = ',';
26+
private const char Space = ' ';
27+
private const char Colon = ':';
28+
private const char Quote = '"';
29+
30+
public static string ToRfc1123String(this DateTimeOffset dateTime)
31+
{
32+
return ToRfc1123String(dateTime, false);
33+
}
34+
35+
public static string ToRfc1123String(this DateTimeOffset dateTime, bool quoted)
36+
{
37+
var universal = dateTime.UtcDateTime;
38+
39+
var length = quoted ? QuotedRfc1123DateLength : Rfc1123DateLength;
40+
var target = new InplaceStringBuilder(length);
41+
42+
if (quoted)
43+
{
44+
target.Append(Quote);
45+
}
46+
47+
target.Append(DayNames[(int)universal.DayOfWeek]);
48+
target.Append(Comma);
49+
target.Append(Space);
50+
AppendNumber(ref target, universal.Day);
51+
target.Append(Space);
52+
target.Append(MonthNames[universal.Month - 1]);
53+
target.Append(Space);
54+
AppendYear(ref target, universal.Year);
55+
target.Append(Space);
56+
AppendTimeOfDay(ref target, universal.TimeOfDay);
57+
target.Append(Space);
58+
target.Append(Gmt);
59+
60+
if (quoted)
61+
{
62+
target.Append(Quote);
63+
}
64+
65+
return target.ToString();
66+
}
67+
68+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
69+
private static void AppendYear(ref InplaceStringBuilder target, int year)
70+
{
71+
target.Append(GetAsciiChar(year / 1000));
72+
target.Append(GetAsciiChar(year % 1000 / 100));
73+
target.Append(GetAsciiChar(year % 100 / 10));
74+
target.Append(GetAsciiChar(year % 10));
75+
}
76+
77+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
78+
private static void AppendTimeOfDay(ref InplaceStringBuilder target, TimeSpan timeOfDay)
79+
{
80+
AppendNumber(ref target, timeOfDay.Hours);
81+
target.Append(Colon);
82+
AppendNumber(ref target, timeOfDay.Minutes);
83+
target.Append(Colon);
84+
AppendNumber(ref target, timeOfDay.Seconds);
85+
}
86+
87+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
88+
private static void AppendNumber(ref InplaceStringBuilder target, int number)
89+
{
90+
target.Append(GetAsciiChar(number / 10));
91+
target.Append(GetAsciiChar(number % 10));
92+
}
93+
94+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
95+
private static char GetAsciiChar(int value)
96+
{
97+
return (char)(AsciiNumberOffset + value);
98+
}
99+
}
100+
}

src/Microsoft.Net.Http.Headers/HeaderUtilities.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,12 @@ public static bool TryParseDate(string input, out DateTimeOffset result)
220220

221221
public static string FormatDate(DateTimeOffset dateTime)
222222
{
223-
return HttpRuleParser.DateToString(dateTime);
223+
return FormatDate(dateTime, false);
224+
}
225+
226+
public static string FormatDate(DateTimeOffset dateTime, bool quoted)
227+
{
228+
return dateTime.ToRfc1123String(quoted);
224229
}
225230

226231
public static string RemoveQuotes(string input)

src/Microsoft.Net.Http.Headers/HttpRuleParser.cs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -233,12 +233,6 @@ internal static HttpParseResult GetQuotedPairLength(string input, int startIndex
233233
return HttpParseResult.Parsed;
234234
}
235235

236-
internal static string DateToString(DateTimeOffset dateTime)
237-
{
238-
// Format according to RFC1123; 'r' uses invariant info (DateTimeFormatInfo.InvariantInfo)
239-
return dateTime.ToUniversalTime().ToString("r", CultureInfo.InvariantCulture);
240-
}
241-
242236
internal static bool TryStringToDate(string input, out DateTimeOffset result)
243237
{
244238
// Try the various date formats in the order listed above.

src/Microsoft.Net.Http.Headers/RangeConditionHeaderValue.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ public override string ToString()
5353
{
5454
if (_entityTag == null)
5555
{
56-
return HttpRuleParser.DateToString(_lastModified.Value);
56+
return HeaderUtilities.FormatDate(_lastModified.Value);
5757
}
5858
return _entityTag.ToString();
5959
}

src/Microsoft.Net.Http.Headers/project.json

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,12 @@
1919
"xmlDoc": true
2020
},
2121
"dependencies": {
22-
"NETStandard.Library": "1.6.1-*"
22+
"Microsoft.Extensions.Primitives": "1.1.0-*",
23+
"NETStandard.Library": "1.6.1-*",
24+
"System.Buffers": "4.3.0-*",
25+
"System.Diagnostics.Contracts": "4.3.0-*"
2326
},
2427
"frameworks": {
25-
"netstandard1.1": {
26-
"dependencies": {
27-
"System.Buffers": "4.3.0-*",
28-
"System.Diagnostics.Contracts": "4.3.0-*"
29-
}
30-
}
28+
"netstandard1.1": {}
3129
}
3230
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using Xunit;
6+
7+
namespace Microsoft.Net.Http.Headers
8+
{
9+
public static class HeaderUtilitiesTest
10+
{
11+
private const string Rfc1123Format = "r";
12+
13+
[Theory]
14+
[MemberData(nameof(TestValues))]
15+
public static void ReturnsSameResultAsRfc1123String(DateTimeOffset dateTime, bool quoted)
16+
{
17+
var formatted = dateTime.ToString(Rfc1123Format);
18+
var expected = quoted ? $"\"{formatted}\"" : formatted;
19+
var actual = HeaderUtilities.FormatDate(dateTime, quoted);
20+
21+
Assert.Equal(expected, actual);
22+
}
23+
24+
public static TheoryData<DateTimeOffset, bool> TestValues
25+
{
26+
get
27+
{
28+
var data = new TheoryData<DateTimeOffset, bool>();
29+
30+
var now = DateTimeOffset.Now;
31+
32+
foreach (var quoted in new[] { true, false })
33+
{
34+
for (var i = 0; i < 60; i++)
35+
{
36+
data.Add(now.AddSeconds(i), quoted);
37+
data.Add(now.AddMinutes(i), quoted);
38+
data.Add(now.AddDays(i), quoted);
39+
data.Add(now.AddMonths(i), quoted);
40+
data.Add(now.AddYears(i), quoted);
41+
}
42+
}
43+
44+
return data;
45+
}
46+
}
47+
}
48+
}

0 commit comments

Comments
 (0)