Added custom RFC 1123 DateTimeFormatter to improve allocation profile (#716)
This commit is contained in:
parent
874dcebbcb
commit
063d6eca0f
|
|
@ -321,7 +321,7 @@ namespace Microsoft.Net.Http.Headers
|
|||
else
|
||||
{
|
||||
// Must always be quoted
|
||||
var dateString = string.Format(CultureInfo.InvariantCulture, "\"{0}\"", HttpRuleParser.DateToString(date.Value));
|
||||
var dateString = HeaderUtilities.FormatDate(date.Value, quoted: true);
|
||||
if (dateParameter != null)
|
||||
{
|
||||
dateParameter.Value = dateString;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,100 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
||||
namespace Microsoft.Net.Http.Headers
|
||||
{
|
||||
internal static class DateTimeFormatter
|
||||
{
|
||||
private static readonly DateTimeFormatInfo FormatInfo = CultureInfo.InvariantCulture.DateTimeFormat;
|
||||
|
||||
private static readonly string[] MonthNames = FormatInfo.AbbreviatedMonthNames;
|
||||
private static readonly string[] DayNames = FormatInfo.AbbreviatedDayNames;
|
||||
|
||||
private static readonly int Rfc1123DateLength = "ddd, dd MMM yyyy HH:mm:ss GMT".Length;
|
||||
private static readonly int QuotedRfc1123DateLength = Rfc1123DateLength + 2;
|
||||
|
||||
// ASCII numbers are in the range 48 - 57.
|
||||
private const int AsciiNumberOffset = 0x30;
|
||||
|
||||
private const string Gmt = "GMT";
|
||||
private const char Comma = ',';
|
||||
private const char Space = ' ';
|
||||
private const char Colon = ':';
|
||||
private const char Quote = '"';
|
||||
|
||||
public static string ToRfc1123String(this DateTimeOffset dateTime)
|
||||
{
|
||||
return ToRfc1123String(dateTime, false);
|
||||
}
|
||||
|
||||
public static string ToRfc1123String(this DateTimeOffset dateTime, bool quoted)
|
||||
{
|
||||
var universal = dateTime.UtcDateTime;
|
||||
|
||||
var length = quoted ? QuotedRfc1123DateLength : Rfc1123DateLength;
|
||||
var target = new InplaceStringBuilder(length);
|
||||
|
||||
if (quoted)
|
||||
{
|
||||
target.Append(Quote);
|
||||
}
|
||||
|
||||
target.Append(DayNames[(int)universal.DayOfWeek]);
|
||||
target.Append(Comma);
|
||||
target.Append(Space);
|
||||
AppendNumber(ref target, universal.Day);
|
||||
target.Append(Space);
|
||||
target.Append(MonthNames[universal.Month - 1]);
|
||||
target.Append(Space);
|
||||
AppendYear(ref target, universal.Year);
|
||||
target.Append(Space);
|
||||
AppendTimeOfDay(ref target, universal.TimeOfDay);
|
||||
target.Append(Space);
|
||||
target.Append(Gmt);
|
||||
|
||||
if (quoted)
|
||||
{
|
||||
target.Append(Quote);
|
||||
}
|
||||
|
||||
return target.ToString();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void AppendYear(ref InplaceStringBuilder target, int year)
|
||||
{
|
||||
target.Append(GetAsciiChar(year / 1000));
|
||||
target.Append(GetAsciiChar(year % 1000 / 100));
|
||||
target.Append(GetAsciiChar(year % 100 / 10));
|
||||
target.Append(GetAsciiChar(year % 10));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void AppendTimeOfDay(ref InplaceStringBuilder target, TimeSpan timeOfDay)
|
||||
{
|
||||
AppendNumber(ref target, timeOfDay.Hours);
|
||||
target.Append(Colon);
|
||||
AppendNumber(ref target, timeOfDay.Minutes);
|
||||
target.Append(Colon);
|
||||
AppendNumber(ref target, timeOfDay.Seconds);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void AppendNumber(ref InplaceStringBuilder target, int number)
|
||||
{
|
||||
target.Append(GetAsciiChar(number / 10));
|
||||
target.Append(GetAsciiChar(number % 10));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static char GetAsciiChar(int value)
|
||||
{
|
||||
return (char)(AsciiNumberOffset + value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -220,7 +220,12 @@ namespace Microsoft.Net.Http.Headers
|
|||
|
||||
public static string FormatDate(DateTimeOffset dateTime)
|
||||
{
|
||||
return HttpRuleParser.DateToString(dateTime);
|
||||
return FormatDate(dateTime, false);
|
||||
}
|
||||
|
||||
public static string FormatDate(DateTimeOffset dateTime, bool quoted)
|
||||
{
|
||||
return dateTime.ToRfc1123String(quoted);
|
||||
}
|
||||
|
||||
public static string RemoveQuotes(string input)
|
||||
|
|
|
|||
|
|
@ -233,12 +233,6 @@ namespace Microsoft.Net.Http.Headers
|
|||
return HttpParseResult.Parsed;
|
||||
}
|
||||
|
||||
internal static string DateToString(DateTimeOffset dateTime)
|
||||
{
|
||||
// Format according to RFC1123; 'r' uses invariant info (DateTimeFormatInfo.InvariantInfo)
|
||||
return dateTime.ToUniversalTime().ToString("r", CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
internal static bool TryStringToDate(string input, out DateTimeOffset result)
|
||||
{
|
||||
// Try the various date formats in the order listed above.
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ namespace Microsoft.Net.Http.Headers
|
|||
{
|
||||
if (_entityTag == null)
|
||||
{
|
||||
return HttpRuleParser.DateToString(_lastModified.Value);
|
||||
return HeaderUtilities.FormatDate(_lastModified.Value);
|
||||
}
|
||||
return _entityTag.ToString();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,14 +19,12 @@
|
|||
"xmlDoc": true
|
||||
},
|
||||
"dependencies": {
|
||||
"NETStandard.Library": "1.6.1-*"
|
||||
"Microsoft.Extensions.Primitives": "1.1.0-*",
|
||||
"NETStandard.Library": "1.6.1-*",
|
||||
"System.Buffers": "4.3.0-*",
|
||||
"System.Diagnostics.Contracts": "4.3.0-*"
|
||||
},
|
||||
"frameworks": {
|
||||
"netstandard1.1": {
|
||||
"dependencies": {
|
||||
"System.Buffers": "4.3.0-*",
|
||||
"System.Diagnostics.Contracts": "4.3.0-*"
|
||||
}
|
||||
}
|
||||
"netstandard1.1": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.Net.Http.Headers
|
||||
{
|
||||
public static class HeaderUtilitiesTest
|
||||
{
|
||||
private const string Rfc1123Format = "r";
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(TestValues))]
|
||||
public static void ReturnsSameResultAsRfc1123String(DateTimeOffset dateTime, bool quoted)
|
||||
{
|
||||
var formatted = dateTime.ToString(Rfc1123Format);
|
||||
var expected = quoted ? $"\"{formatted}\"" : formatted;
|
||||
var actual = HeaderUtilities.FormatDate(dateTime, quoted);
|
||||
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
public static TheoryData<DateTimeOffset, bool> TestValues
|
||||
{
|
||||
get
|
||||
{
|
||||
var data = new TheoryData<DateTimeOffset, bool>();
|
||||
|
||||
var now = DateTimeOffset.Now;
|
||||
|
||||
foreach (var quoted in new[] { true, false })
|
||||
{
|
||||
for (var i = 0; i < 60; i++)
|
||||
{
|
||||
data.Add(now.AddSeconds(i), quoted);
|
||||
data.Add(now.AddMinutes(i), quoted);
|
||||
data.Add(now.AddDays(i), quoted);
|
||||
data.Add(now.AddMonths(i), quoted);
|
||||
data.Add(now.AddYears(i), quoted);
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue