Last active
September 30, 2016 13:55
-
-
Save khellang/3b32c0fd453616bb2de7edabf06e4413 to your computer and use it in GitHub Desktop.
Less allocatey DateTimeOffset RFC1123 formatting
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using static System.Text.Encoding; | |
using BenchmarkDotNet.Attributes; | |
using BenchmarkDotNet.Configs; | |
using BenchmarkDotNet.Diagnostics.Windows; | |
using BenchmarkDotNet.Running; | |
using Xunit; | |
public static class Program | |
{ | |
public static void Main(string[] args) | |
{ | |
BenchmarkRunner.Run<Benchmark>(); | |
} | |
public class Config : ManualConfig | |
{ | |
public Config() | |
{ | |
Add(new MemoryDiagnoser()); | |
} | |
} | |
[Config(typeof(Config))] | |
public class Benchmark | |
{ | |
private readonly DateTimeOffset _dateTime = DateTimeOffset.UtcNow; | |
[Benchmark(Baseline = true)] | |
public string ToStringR() | |
{ | |
return _dateTime.ToString("R"); | |
} | |
[Benchmark] | |
public string ToRfc1123String() | |
{ | |
return _dateTime.ToRfc1123String(); | |
} | |
} | |
[Theory] | |
[MemberData(nameof(TestValues))] | |
public static void FormatterReturnsSameResultAsToStringR(DateTimeOffset dateTime) | |
{ | |
var expected = dateTime.ToString("R"); | |
var actual = dateTime.ToRfc1123String(); | |
Assert.Equal(expected, actual); | |
} | |
public static TheoryData<DateTimeOffset> TestValues | |
{ | |
get | |
{ | |
var data = new TheoryData<DateTimeOffset>(); | |
var now = DateTimeOffset.Now; | |
for (var i = 0; i < 60; i++) | |
{ | |
data.Add(now.AddSeconds(i)); | |
data.Add(now.AddMinutes(i)); | |
data.Add(now.AddDays(i)); | |
data.Add(now.AddMonths(i)); | |
data.Add(now.AddYears(i)); | |
} | |
return data; | |
} | |
} | |
} | |
internal static class DateTimeFormatter | |
{ | |
private static readonly DateTimeFormatInfo FormatInfo = CultureInfo.InvariantCulture.DateTimeFormat; | |
private static readonly byte[] MonBytes = UTF8.GetBytes(GetDayName(DayOfWeek.Monday)); | |
private static readonly byte[] TueBytes = UTF8.GetBytes(GetDayName(DayOfWeek.Tuesday)); | |
private static readonly byte[] WedBytes = UTF8.GetBytes(GetDayName(DayOfWeek.Wednesday)); | |
private static readonly byte[] ThuBytes = UTF8.GetBytes(GetDayName(DayOfWeek.Thursday)); | |
private static readonly byte[] FriBytes = UTF8.GetBytes(GetDayName(DayOfWeek.Friday)); | |
private static readonly byte[] SatBytes = UTF8.GetBytes(GetDayName(DayOfWeek.Saturday)); | |
private static readonly byte[] SunBytes = UTF8.GetBytes(GetDayName(DayOfWeek.Sunday)); | |
private static readonly byte[] JanBytes = UTF8.GetBytes(GetMonthName(1)); | |
private static readonly byte[] FebBytes = UTF8.GetBytes(GetMonthName(2)); | |
private static readonly byte[] MarBytes = UTF8.GetBytes(GetMonthName(3)); | |
private static readonly byte[] AprBytes = UTF8.GetBytes(GetMonthName(4)); | |
private static readonly byte[] MayBytes = UTF8.GetBytes(GetMonthName(5)); | |
private static readonly byte[] JunBytes = UTF8.GetBytes(GetMonthName(6)); | |
private static readonly byte[] JulBytes = UTF8.GetBytes(GetMonthName(7)); | |
private static readonly byte[] AugBytes = UTF8.GetBytes(GetMonthName(8)); | |
private static readonly byte[] SepBytes = UTF8.GetBytes(GetMonthName(9)); | |
private static readonly byte[] OctBytes = UTF8.GetBytes(GetMonthName(10)); | |
private static readonly byte[] NovBytes = UTF8.GetBytes(GetMonthName(11)); | |
private static readonly byte[] DecBytes = UTF8.GetBytes(GetMonthName(12)); | |
private static readonly byte[] GmtBytes = UTF8.GetBytes("GMT"); | |
// The format is "ddd, dd MMM yyyy HH:mm:ss GMT". | |
private const int Rfc1123DateLength = 29; | |
// ASCII numbers are in the range 48 - 57. | |
private const int AsciiNumberOffset = 0x30; | |
private const char Comma = ','; | |
private const char Space = ' '; | |
private const char Colon = ':'; | |
public static unsafe string ToRfc1123String(this DateTimeOffset dateTime) | |
{ | |
var offset = 0; | |
char* target = stackalloc char[Rfc1123DateLength]; | |
var universalDateTime = dateTime.ToUniversalTime(); | |
offset = FormatDayOfWeek(universalDateTime.DayOfWeek, target, offset); | |
offset = Format(Comma, target, offset); | |
offset = Format(Space, target, offset); | |
offset = FormatNumber(universalDateTime.Day, target, offset); | |
offset = Format(Space, target, offset); | |
offset = FormatMonth(universalDateTime.Month, target, offset); | |
offset = Format(Space, target, offset); | |
offset = FormatYear(universalDateTime.Year, target, offset); | |
offset = Format(Space, target, offset); | |
offset = FormatTimeOfDay(universalDateTime.TimeOfDay, target, offset); | |
offset = Format(Space, target, offset); | |
offset = Format(GmtBytes, target, offset); | |
return new string(target, 0, offset); | |
} | |
private static unsafe int FormatDayOfWeek(DayOfWeek dayOfWeek, char* target, int offset) | |
{ | |
switch (dayOfWeek) | |
{ | |
case DayOfWeek.Sunday: return Format(SunBytes, target, offset); | |
case DayOfWeek.Monday: return Format(MonBytes, target, offset); | |
case DayOfWeek.Tuesday: return Format(TueBytes, target, offset); | |
case DayOfWeek.Wednesday: return Format(WedBytes, target, offset); | |
case DayOfWeek.Thursday: return Format(ThuBytes, target, offset); | |
case DayOfWeek.Friday: return Format(FriBytes, target, offset); | |
case DayOfWeek.Saturday: return Format(SatBytes, target, offset); | |
default: return offset; | |
} | |
} | |
private static unsafe int FormatMonth(int month, char* target, int offset) | |
{ | |
switch (month) | |
{ | |
case 1: return Format(JanBytes, target, offset); | |
case 2: return Format(FebBytes, target, offset); | |
case 3: return Format(MarBytes, target, offset); | |
case 4: return Format(AprBytes, target, offset); | |
case 5: return Format(MayBytes, target, offset); | |
case 6: return Format(JunBytes, target, offset); | |
case 7: return Format(JulBytes, target, offset); | |
case 8: return Format(AugBytes, target, offset); | |
case 9: return Format(SepBytes, target, offset); | |
case 10: return Format(OctBytes, target, offset); | |
case 11: return Format(NovBytes, target, offset); | |
case 12: return Format(DecBytes, target, offset); | |
default: return offset; | |
} | |
} | |
private static unsafe int FormatYear(int year, char* target, int offset) | |
{ | |
offset = Format(GetAsciiChar(year / 1000), target, offset); | |
offset = Format(GetAsciiChar(year % 1000 / 100), target, offset); | |
offset = Format(GetAsciiChar(year % 100 / 10), target, offset); | |
offset = Format(GetAsciiChar(year % 10), target, offset); | |
return offset; | |
} | |
private static unsafe int FormatTimeOfDay(TimeSpan timeOfDay, char* target, int offset) | |
{ | |
offset = FormatNumber(timeOfDay.Hours, target, offset); | |
offset = Format(Colon, target, offset); | |
offset = FormatNumber(timeOfDay.Minutes, target, offset); | |
offset = Format(Colon, target, offset); | |
offset = FormatNumber(timeOfDay.Seconds, target, offset); | |
return offset; | |
} | |
private static unsafe int FormatNumber(int number, char* target, int offset) | |
{ | |
offset = Format(GetAsciiChar(number / 10), target, offset); | |
offset = Format(GetAsciiChar(number % 10), target, offset); | |
return offset; | |
} | |
private static unsafe int Format(byte[] source, char* target, int offset) | |
{ | |
foreach (var b in source) | |
{ | |
offset = Format((char) b, target, offset); | |
} | |
return offset; | |
} | |
private static unsafe int Format(char value, char* target, int offset) | |
{ | |
target[offset++] = value; | |
return offset; | |
} | |
private static char GetAsciiChar(int value) | |
{ | |
return (char)(AsciiNumberOffset + value); | |
} | |
private static string GetDayName(DayOfWeek dayOfWeek) | |
{ | |
return FormatInfo.GetAbbreviatedDayName(dayOfWeek); | |
} | |
private static string GetMonthName(int month) | |
{ | |
return FormatInfo.GetAbbreviatedMonthName(month); | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment