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
|
else
|
||||||
{
|
{
|
||||||
// Must always be quoted
|
// 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)
|
if (dateParameter != null)
|
||||||
{
|
{
|
||||||
dateParameter.Value = dateString;
|
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)
|
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)
|
public static string RemoveQuotes(string input)
|
||||||
|
|
|
||||||
|
|
@ -233,12 +233,6 @@ namespace Microsoft.Net.Http.Headers
|
||||||
return HttpParseResult.Parsed;
|
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)
|
internal static bool TryStringToDate(string input, out DateTimeOffset result)
|
||||||
{
|
{
|
||||||
// Try the various date formats in the order listed above.
|
// Try the various date formats in the order listed above.
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ namespace Microsoft.Net.Http.Headers
|
||||||
{
|
{
|
||||||
if (_entityTag == null)
|
if (_entityTag == null)
|
||||||
{
|
{
|
||||||
return HttpRuleParser.DateToString(_lastModified.Value);
|
return HeaderUtilities.FormatDate(_lastModified.Value);
|
||||||
}
|
}
|
||||||
return _entityTag.ToString();
|
return _entityTag.ToString();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,14 +19,12 @@
|
||||||
"xmlDoc": true
|
"xmlDoc": true
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"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": {
|
"frameworks": {
|
||||||
"netstandard1.1": {
|
"netstandard1.1": {}
|
||||||
"dependencies": {
|
|
||||||
"System.Buffers": "4.3.0-*",
|
|
||||||
"System.Diagnostics.Contracts": "4.3.0-*"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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