Added custom RFC 1123 DateTimeFormatter to improve allocation profile (#716)

This commit is contained in:
Kristian Hellang 2016-10-05 21:19:15 +02:00 committed by Pavel Krymets
parent 874dcebbcb
commit 063d6eca0f
7 changed files with 161 additions and 16 deletions

View File

@ -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;

View File

@ -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);
}
}
}

View File

@ -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)

View File

@ -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.

View File

@ -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();
}

View File

@ -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": {}
}
}

View File

@ -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;
}
}
}
}