Skip to content

Instantly share code, notes, and snippets.

@aL3891
Last active October 6, 2016 08:48
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save aL3891/2644c97e55f2f6c67b1ade15228031b8 to your computer and use it in GitHub Desktop.
Save aL3891/2644c97e55f2f6c67b1ade15228031b8 to your computer and use it in GitHub Desktop.
dateformat
using System;
using static System.Text.Encoding;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Diagnostics.Windows;
using BenchmarkDotNet.Running;
using Xunit;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Linq;
public static class Program
{
public unsafe static void Main(string[] args)
{
BenchmarkRunner.Run<Benchmark>();
Console.ReadLine();
}
public class Config : ManualConfig
{
public Config()
{
Add(new MemoryDiagnoser());
}
}
[Config(typeof(Config))]
public class Benchmark
{
private readonly DateTimeOffset _dateTime = DateTimeOffset.UtcNow;
private byte[] buf = new byte[29];
[Benchmark(Baseline = true)]
public string ToStringR()
{
return _dateTime.ToString("R");
}
[Benchmark]
public string ToRfc1123StringKristian()
{
return DateTimeFormatterKristian.ToRfc1123String(_dateTime);
}
[Benchmark]
public string ToRfc1123StringTore()
{
return DateTimeFormatterTore.ToRfc1123String(_dateTime);
}
[Benchmark]
public string ToRfc1123StringAllanv1()
{
return DateTimeFormatterAllanV1.ToRfc1123String(_dateTime);
}
[Benchmark]
public string ToRfc1123StringAllanv2()
{
return DateTimeFormatterAllanV2.ToRfc1123String(_dateTime);
}
[Benchmark]
public string ToRfc1123StringAllanv3()
{
return DateTimeFormatterAllanV3.ToRfc1123String(_dateTime);
}
[Benchmark]
public string ToRfc1123StringAllanv4()
{
return DateTimeFormatterAllanV4.ToRfc1123String(_dateTime);
}
[Benchmark()]
public unsafe void ToRfc1123StringAllanv1WithBuffer()
{
fixed (byte* b = &buf[0])
DateTimeFormatterAllanV1.ToRfc1123String(_dateTime, b, 0);
}
[Benchmark]
public unsafe void ToRfc1123StringAllanv2WithBuffer()
{
fixed (byte* b = &buf[0])
DateTimeFormatterAllanV2.ToRfc1123String(_dateTime, b, 0);
}
[Benchmark]
public unsafe void ToRfc1123StringAllanv3WithBuffer()
{
fixed (byte* b = &buf[0])
DateTimeFormatterAllanV3.ToRfc1123String(_dateTime, b, 0);
}
[Benchmark]
public unsafe void ToRfc1123StringAllanv4WithBuffer()
{
fixed (byte* b = &buf[0])
DateTimeFormatterAllanV4.ToRfc1123String(_dateTime, b, 0);
}
}
}
internal static unsafe class DateTimeFormatterAllanV1
{
private static readonly DateTimeFormatInfo FormatInfo = CultureInfo.InvariantCulture.DateTimeFormat;
private static readonly byte[] dayNames = UTF8.GetBytes(string.Join("", FormatInfo.AbbreviatedDayNames));
private static readonly byte[] months = UTF8.GetBytes(string.Join("", FormatInfo.AbbreviatedMonthGenitiveNames));
private static readonly byte[] years = UTF8.GetBytes(string.Join("", Enumerable.Range(2015, 10).Select(y => y.ToString("0000"))));
private static readonly byte[] numbers = UTF8.GetBytes(string.Join("", Enumerable.Range(0, 60).Select(y => y.ToString("00"))));
private static readonly byte[] templateBytes = UTF8.GetBytes("ddd, dd MMM yyyy HH:mm:ss GMT");
public static unsafe string ToRfc1123String(this DateTimeOffset dateTime)
{
byte* buf = stackalloc byte[29];
ToRfc1123String(dateTime, buf, 0);
return UTF8.GetString(buf, 29);
}
public static unsafe void ToRfc1123String(this DateTimeOffset dto, byte* target, int offset)
{
var tmpIndex = 0;
var dateTime = dto.UtcDateTime;
fixed (byte* t = &templateBytes[0])
Unsafe.CopyBlock(target, t, (uint)templateBytes.Length);
switch (dateTime.DayOfWeek)
{
case DayOfWeek.Sunday: target[0] = dayNames[(0 * 3) + 0]; target[1] = dayNames[(0 * 3) + 1]; target[2] = dayNames[(0 * 3) + 2]; break;
case DayOfWeek.Monday: target[0] = dayNames[(1 * 3) + 0]; target[1] = dayNames[(1 * 3) + 1]; target[2] = dayNames[(1 * 3) + 2]; break;
case DayOfWeek.Tuesday: target[0] = dayNames[(2 * 3) + 0]; target[1] = dayNames[(2 * 3) + 1]; target[2] = dayNames[(2 * 3) + 2]; break;
case DayOfWeek.Wednesday: target[0] = dayNames[(3 * 3) + 0]; target[1] = dayNames[(3 * 3) + 1]; target[2] = dayNames[(3 * 3) + 2]; break;
case DayOfWeek.Thursday: target[0] = dayNames[(4 * 3) + 0]; target[1] = dayNames[(4 * 3) + 1]; target[2] = dayNames[(4 * 3) + 2]; break;
case DayOfWeek.Friday: target[0] = dayNames[(5 * 3) + 0]; target[1] = dayNames[(5 * 3) + 1]; target[2] = dayNames[(5 * 3) + 2]; break;
case DayOfWeek.Saturday: target[0] = dayNames[(6 * 3) + 0]; target[1] = dayNames[(6 * 3) + 1]; target[2] = dayNames[(6 * 3) + 2]; break;
}
tmpIndex = dateTime.Day * 2;
target[5] = numbers[tmpIndex];
target[6] = numbers[tmpIndex + 1];
switch (dateTime.Month)
{
case 1: target[8] = months[(0 * 3) + 0]; target[9] = months[(0 * 3) + 1]; target[10] = months[(0 * 3) + 2]; break;
case 2: target[8] = months[(1 * 3) + 0]; target[9] = months[(1 * 3) + 1]; target[10] = months[(1 * 3) + 2]; break;
case 3: target[8] = months[(2 * 3) + 0]; target[9] = months[(2 * 3) + 1]; target[10] = months[(2 * 3) + 2]; break;
case 4: target[8] = months[(3 * 3) + 0]; target[9] = months[(3 * 3) + 1]; target[10] = months[(3 * 3) + 2]; break;
case 5: target[8] = months[(4 * 3) + 0]; target[9] = months[(4 * 3) + 1]; target[10] = months[(4 * 3) + 2]; break;
case 6: target[8] = months[(5 * 3) + 0]; target[9] = months[(5 * 3) + 1]; target[10] = months[(5 * 3) + 2]; break;
case 7: target[8] = months[(6 * 3) + 0]; target[9] = months[(6 * 3) + 1]; target[10] = months[(6 * 3) + 2]; break;
case 8: target[8] = months[(7 * 3) + 0]; target[9] = months[(7 * 3) + 1]; target[10] = months[(7 * 3) + 2]; break;
case 9: target[8] = months[(8 * 3) + 0]; target[9] = months[(8 * 3) + 1]; target[10] = months[(8 * 3) + 2]; break;
case 10: target[8] = months[(9 * 3) + 0]; target[9] = months[(9 * 3) + 1]; target[10] = months[(9 * 3) + 2]; break;
case 11: target[8] = months[(10 * 3) + 0]; target[9] = months[(10 * 3) + 1]; target[10] = months[(10 * 3) + 2]; break;
case 12: target[8] = months[(11 * 3) + 0]; target[9] = months[(11 * 3) + 1]; target[10] = months[(11 * 3) + 2]; break;
}
tmpIndex = (dateTime.Year - 2015) * 4;
target[12] = years[tmpIndex];
target[13] = years[tmpIndex + 1];
target[14] = years[tmpIndex + 2];
target[15] = years[tmpIndex + 3];
tmpIndex = dateTime.Hour * 2;
target[17] = numbers[tmpIndex];
target[18] = numbers[tmpIndex + 1];
tmpIndex = dateTime.Minute * 2;
target[20] = numbers[tmpIndex];
target[21] = numbers[tmpIndex + 1];
tmpIndex = dateTime.Second * 2;
target[23] = numbers[tmpIndex];
target[24] = numbers[tmpIndex + 1];
}
}
internal static class DateTimeFormatterAllanV2
{
private static unsafe byte* dayNames;
private static unsafe byte* months;
private static unsafe byte* numbers;
private static unsafe byte* template;
private static unsafe byte* years;
static unsafe DateTimeFormatterAllanV2()
{
var FormatInfo = CultureInfo.InvariantCulture.DateTimeFormat;
var dayNameBytes = UTF8.GetBytes(string.Join("", FormatInfo.AbbreviatedDayNames));
dayNames = (byte*)Marshal.AllocHGlobal(dayNameBytes.Length).ToPointer();
fixed (byte* dn = &dayNameBytes[0])
Buffer.MemoryCopy(dn, dayNames, dayNameBytes.Length, dayNameBytes.Length);
var monthsBytes = UTF8.GetBytes(string.Join("", FormatInfo.AbbreviatedMonthGenitiveNames));
months = (byte*)Marshal.AllocHGlobal(monthsBytes.Length).ToPointer();
fixed (byte* m = &monthsBytes[0])
Buffer.MemoryCopy(m, months, monthsBytes.Length, monthsBytes.Length);
var yearsBytes = UTF8.GetBytes(string.Join("", Enumerable.Range(2015, 10).Select(y => y.ToString("0000"))));
years = (byte*)Marshal.AllocHGlobal(yearsBytes.Length).ToPointer();
fixed (byte* y = &yearsBytes[0])
Buffer.MemoryCopy(y, years, yearsBytes.Length, yearsBytes.Length);
var numbersBytes = UTF8.GetBytes(string.Join("", Enumerable.Range(0, 60).Select(y => y.ToString("00"))));
numbers = (byte*)Marshal.AllocHGlobal(numbersBytes.Length).ToPointer();
fixed (byte* n = &numbersBytes[0])
Buffer.MemoryCopy(n, numbers, numbersBytes.Length, numbersBytes.Length);
var templateBytes = UTF8.GetBytes("ddd, dd MMM yyyy HH:mm:ss GMT");
template = (byte*)Marshal.AllocHGlobal(templateBytes.Length).ToPointer();
fixed (byte* t = &templateBytes[0])
Buffer.MemoryCopy(t, template, templateBytes.Length, templateBytes.Length);
}
public static unsafe string ToRfc1123String(DateTimeOffset dateTime)
{
byte* buf = stackalloc byte[29];
ToRfc1123String(dateTime, buf, 0);
return UTF8.GetString(buf, 29);
}
public static unsafe void ToRfc1123String(DateTimeOffset dto, byte* target, int offset)
{
var tmpIndex = 0;
var dateTime = dto.UtcDateTime;
Unsafe.CopyBlock(target, template, 29);
switch (dateTime.DayOfWeek)
{
case DayOfWeek.Sunday: target[0] = dayNames[(0 * 3) + 0]; target[1] = dayNames[(0 * 3) + 1]; target[2] = dayNames[(0 * 3) + 2]; break;
case DayOfWeek.Monday: target[0] = dayNames[(1 * 3) + 0]; target[1] = dayNames[(1 * 3) + 1]; target[2] = dayNames[(1 * 3) + 2]; break;
case DayOfWeek.Tuesday: target[0] = dayNames[(2 * 3) + 0]; target[1] = dayNames[(2 * 3) + 1]; target[2] = dayNames[(2 * 3) + 2]; break;
case DayOfWeek.Wednesday: target[0] = dayNames[(3 * 3) + 0]; target[1] = dayNames[(3 * 3) + 1]; target[2] = dayNames[(3 * 3) + 2]; break;
case DayOfWeek.Thursday: target[0] = dayNames[(4 * 3) + 0]; target[1] = dayNames[(4 * 3) + 1]; target[2] = dayNames[(4 * 3) + 2]; break;
case DayOfWeek.Friday: target[0] = dayNames[(5 * 3) + 0]; target[1] = dayNames[(5 * 3) + 1]; target[2] = dayNames[(5 * 3) + 2]; break;
case DayOfWeek.Saturday: target[0] = dayNames[(6 * 3) + 0]; target[1] = dayNames[(6 * 3) + 1]; target[2] = dayNames[(6 * 3) + 2]; break;
}
tmpIndex = dateTime.Day * 2;
target[5] = numbers[tmpIndex];
target[6] = numbers[tmpIndex + 1];
switch (dateTime.Month)
{
case 1: target[8] = months[(0 * 3) + 0]; target[9] = months[(0 * 3) + 1]; target[10] = months[(0 * 3) + 2]; break;
case 2: target[8] = months[(1 * 3) + 0]; target[9] = months[(1 * 3) + 1]; target[10] = months[(1 * 3) + 2]; break;
case 3: target[8] = months[(2 * 3) + 0]; target[9] = months[(2 * 3) + 1]; target[10] = months[(2 * 3) + 2]; break;
case 4: target[8] = months[(3 * 3) + 0]; target[9] = months[(3 * 3) + 1]; target[10] = months[(3 * 3) + 2]; break;
case 5: target[8] = months[(4 * 3) + 0]; target[9] = months[(4 * 3) + 1]; target[10] = months[(4 * 3) + 2]; break;
case 6: target[8] = months[(5 * 3) + 0]; target[9] = months[(5 * 3) + 1]; target[10] = months[(5 * 3) + 2]; break;
case 7: target[8] = months[(6 * 3) + 0]; target[9] = months[(6 * 3) + 1]; target[10] = months[(6 * 3) + 2]; break;
case 8: target[8] = months[(7 * 3) + 0]; target[9] = months[(7 * 3) + 1]; target[10] = months[(7 * 3) + 2]; break;
case 9: target[8] = months[(8 * 3) + 0]; target[9] = months[(8 * 3) + 1]; target[10] = months[(8 * 3) + 2]; break;
case 10: target[8] = months[(9 * 3) + 0]; target[9] = months[(9 * 3) + 1]; target[10] = months[(9 * 3) + 2]; break;
case 11: target[8] = months[(10 * 3) + 0]; target[9] = months[(10 * 3) + 1]; target[10] = months[(10 * 3) + 2]; break;
case 12: target[8] = months[(11 * 3) + 0]; target[9] = months[(11 * 3) + 1]; target[10] = months[(11 * 3) + 2]; break;
}
tmpIndex = (dateTime.Year - 2015) * 4;
target[12] = years[tmpIndex];
target[13] = years[tmpIndex + 1];
target[14] = years[tmpIndex + 2];
target[15] = years[tmpIndex + 3];
tmpIndex = dateTime.Hour * 2;
target[17] = numbers[tmpIndex];
target[18] = numbers[tmpIndex + 1];
tmpIndex = dateTime.Minute * 2;
target[20] = numbers[tmpIndex];
target[21] = numbers[tmpIndex + 1];
tmpIndex = dateTime.Second * 2;
target[23] = numbers[tmpIndex];
target[24] = numbers[tmpIndex + 1];
}
}
internal static class DateTimeFormatterAllanV3
{
private const long TicksPerSecond = 10000000L;
private const long TicksPerDay = 864000000000L;
private const int DaysPerYear = 365;
private const int DaysPer4Years = 1461;
private const int DaysPer100Years = 36524;
private const int DaysPer400Years = 146097;
private static readonly int[] DaysToMonth365 = new int[] { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 };
private static readonly int[] DaysToMonth366 = new int[] { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 };
private static unsafe byte* dayNames;
private static unsafe byte* months;
private static unsafe byte* numbers;
private static unsafe byte* template;
private static unsafe byte* years;
static unsafe DateTimeFormatterAllanV3()
{
var FormatInfo = CultureInfo.InvariantCulture.DateTimeFormat;
var dayNameBytes = UTF8.GetBytes(string.Join("", FormatInfo.AbbreviatedDayNames));
dayNames = (byte*)Marshal.AllocHGlobal(dayNameBytes.Length).ToPointer();
fixed (byte* dn = &dayNameBytes[0])
Buffer.MemoryCopy(dn, dayNames, dayNameBytes.Length, dayNameBytes.Length);
var monthsBytes = UTF8.GetBytes(string.Join("", FormatInfo.AbbreviatedMonthGenitiveNames));
months = (byte*)Marshal.AllocHGlobal(monthsBytes.Length).ToPointer();
fixed (byte* m = &monthsBytes[0])
Buffer.MemoryCopy(m, months, monthsBytes.Length, monthsBytes.Length);
var yearsBytes = UTF8.GetBytes(string.Join("", Enumerable.Range(2015, 10).Select(y => y.ToString("0000"))));
years = (byte*)Marshal.AllocHGlobal(yearsBytes.Length).ToPointer();
fixed (byte* y = &yearsBytes[0])
Buffer.MemoryCopy(y, years, yearsBytes.Length, yearsBytes.Length);
var numbersBytes = UTF8.GetBytes(string.Join("", Enumerable.Range(0, 60).Select(y => y.ToString("00"))));
numbers = (byte*)Marshal.AllocHGlobal(numbersBytes.Length).ToPointer();
fixed (byte* n = &numbersBytes[0])
Buffer.MemoryCopy(n, numbers, numbersBytes.Length, numbersBytes.Length);
var templateBytes = UTF8.GetBytes("ddd, dd MMM yyyy HH:mm:ss GMT");
template = (byte*)Marshal.AllocHGlobal(templateBytes.Length).ToPointer();
fixed (byte* t = &templateBytes[0])
Buffer.MemoryCopy(t, template, templateBytes.Length, templateBytes.Length);
}
public static unsafe string ToRfc1123String(DateTimeOffset dateTime)
{
byte* buf = stackalloc byte[29];
ToRfc1123String(dateTime, buf, 0);
return UTF8.GetString(buf, 29);
}
public static unsafe void ToRfc1123String(DateTimeOffset dto, byte* target, int offset)
{
var tmpIndex = 0;
var dateTime = dto.UtcDateTime;
Unsafe.CopyBlock(target, template, 29);
//https://github.com/dotnet/coreclr/blob/61cb42ceb8f6f542606c7863c7e26edea7b9653c/src/mscorlib/src/System/DateTime.cs#L742
Int64 ticks = dateTime.Ticks;
// n = number of days since 1/1/0001
int n = (int)(ticks / TicksPerDay);
// y400 = number of whole 400-year periods since 1/1/0001
int y400 = n / DaysPer400Years;
// n = day number within 400-year period
n -= y400 * DaysPer400Years;
// y100 = number of whole 100-year periods within 400-year period
int y100 = n / DaysPer100Years;
// Last 100-year period has an extra day, so decrement result if 4
if (y100 == 4) y100 = 3;
// n = day number within 100-year period
n -= y100 * DaysPer100Years;
// y4 = number of whole 4-year periods within 100-year period
int y4 = n / DaysPer4Years;
// n = day number within 4-year period
n -= y4 * DaysPer4Years;
// y1 = number of whole years within 4-year period
int y1 = n / DaysPerYear;
// Last year has an extra day, so decrement result if 4
if (y1 == 4) y1 = 3;
// If year was requested, compute and return it
var year = y400 * 400 + y100 * 100 + y4 * 4 + y1 + 1;
// n = day number within year
n -= y1 * DaysPerYear;
// If day-of-year was requested, return it
// Leap year calculation looks different from IsLeapYear since y1, y4,
// and y100 are relative to year 1, not year 0
bool leapYear = y1 == 3 && (y4 != 24 || y100 == 3);
int[] days = leapYear ? DaysToMonth366 : DaysToMonth365;
// All months have less than 32 days, so n >> 5 is a good conservative
// estimate for the month
int month = n >> 5 + 1;
// m = 1-based month number
while (n >= days[month])
month++;
// If month was requested, return it
// Return 1-based day-of-month
var day = n - days[month - 1] + 1;
switch (dateTime.DayOfWeek)
{
case DayOfWeek.Sunday: target[0] = dayNames[(0 * 3) + 0]; target[1] = dayNames[(0 * 3) + 1]; target[2] = dayNames[(0 * 3) + 2]; break;
case DayOfWeek.Monday: target[0] = dayNames[(1 * 3) + 0]; target[1] = dayNames[(1 * 3) + 1]; target[2] = dayNames[(1 * 3) + 2]; break;
case DayOfWeek.Tuesday: target[0] = dayNames[(2 * 3) + 0]; target[1] = dayNames[(2 * 3) + 1]; target[2] = dayNames[(2 * 3) + 2]; break;
case DayOfWeek.Wednesday: target[0] = dayNames[(3 * 3) + 0]; target[1] = dayNames[(3 * 3) + 1]; target[2] = dayNames[(3 * 3) + 2]; break;
case DayOfWeek.Thursday: target[0] = dayNames[(4 * 3) + 0]; target[1] = dayNames[(4 * 3) + 1]; target[2] = dayNames[(4 * 3) + 2]; break;
case DayOfWeek.Friday: target[0] = dayNames[(5 * 3) + 0]; target[1] = dayNames[(5 * 3) + 1]; target[2] = dayNames[(5 * 3) + 2]; break;
case DayOfWeek.Saturday: target[0] = dayNames[(6 * 3) + 0]; target[1] = dayNames[(6 * 3) + 1]; target[2] = dayNames[(6 * 3) + 2]; break;
}
tmpIndex = day * 2;
target[5] = numbers[tmpIndex];
target[6] = numbers[tmpIndex + 1];
switch (month)
{
case 1: target[8] = months[(0 * 3) + 0]; target[9] = months[(0 * 3) + 1]; target[10] = months[(0 * 3) + 2]; break;
case 2: target[8] = months[(1 * 3) + 0]; target[9] = months[(1 * 3) + 1]; target[10] = months[(1 * 3) + 2]; break;
case 3: target[8] = months[(2 * 3) + 0]; target[9] = months[(2 * 3) + 1]; target[10] = months[(2 * 3) + 2]; break;
case 4: target[8] = months[(3 * 3) + 0]; target[9] = months[(3 * 3) + 1]; target[10] = months[(3 * 3) + 2]; break;
case 5: target[8] = months[(4 * 3) + 0]; target[9] = months[(4 * 3) + 1]; target[10] = months[(4 * 3) + 2]; break;
case 6: target[8] = months[(5 * 3) + 0]; target[9] = months[(5 * 3) + 1]; target[10] = months[(5 * 3) + 2]; break;
case 7: target[8] = months[(6 * 3) + 0]; target[9] = months[(6 * 3) + 1]; target[10] = months[(6 * 3) + 2]; break;
case 8: target[8] = months[(7 * 3) + 0]; target[9] = months[(7 * 3) + 1]; target[10] = months[(7 * 3) + 2]; break;
case 9: target[8] = months[(8 * 3) + 0]; target[9] = months[(8 * 3) + 1]; target[10] = months[(8 * 3) + 2]; break;
case 10: target[8] = months[(9 * 3) + 0]; target[9] = months[(9 * 3) + 1]; target[10] = months[(9 * 3) + 2]; break;
case 11: target[8] = months[(10 * 3) + 0]; target[9] = months[(10 * 3) + 1]; target[10] = months[(10 * 3) + 2]; break;
case 12: target[8] = months[(11 * 3) + 0]; target[9] = months[(11 * 3) + 1]; target[10] = months[(11 * 3) + 2]; break;
}
tmpIndex = (year - 2015) * 4;
target[12] = years[tmpIndex];
target[13] = years[tmpIndex + 1];
target[14] = years[tmpIndex + 2];
target[15] = years[tmpIndex + 3];
tmpIndex = dateTime.Hour * 2;
target[17] = numbers[tmpIndex];
target[18] = numbers[tmpIndex + 1];
tmpIndex = dateTime.Minute * 2;
target[20] = numbers[tmpIndex];
target[21] = numbers[tmpIndex + 1];
tmpIndex = dateTime.Second * 2;
target[23] = numbers[tmpIndex];
target[24] = numbers[tmpIndex + 1];
}
}
internal static class DateTimeFormatterAllanV4
{
private const long TicksPerSecond = 10000000L;
private const long TicksPerDay = 864000000000L;
private const int DaysPerYear = 365;
private const int DaysPer4Years = 1461;
private const int DaysPer100Years = 36524;
private const int DaysPer400Years = 146097;
private static readonly int[] DaysToMonth365 = new int[] { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 };
private static readonly int[] DaysToMonth366 = new int[] { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 };
private static volatile unsafe byte* dayNames;
private static volatile unsafe byte* months;
private static volatile unsafe short* numbers;
private static volatile unsafe byte* template;
private static volatile unsafe int* years;
static unsafe DateTimeFormatterAllanV4()
{
var FormatInfo = CultureInfo.InvariantCulture.DateTimeFormat;
var dayNameBytes = UTF8.GetBytes(string.Join("", FormatInfo.AbbreviatedDayNames));
dayNames = (byte*)Marshal.AllocHGlobal(dayNameBytes.Length).ToPointer();
fixed (byte* dn = &dayNameBytes[0])
Unsafe.CopyBlock(dayNames, dn, (uint)dayNameBytes.Length);
var monthsBytes = UTF8.GetBytes(string.Join("", FormatInfo.AbbreviatedMonthGenitiveNames));
months = (byte*)Marshal.AllocHGlobal(monthsBytes.Length).ToPointer();
fixed (byte* m = &monthsBytes[0])
Unsafe.CopyBlock(months, m, (uint)monthsBytes.Length);
var yearsBytes = UTF8.GetBytes(string.Join("", Enumerable.Range(2000, 50).Select(y => y.ToString("0000"))));
years = (int*)Marshal.AllocHGlobal(yearsBytes.Length).ToPointer();
fixed (byte* y = &yearsBytes[0])
Unsafe.CopyBlock(years, y, (uint)yearsBytes.Length);
var numbersBytes = UTF8.GetBytes(string.Join("", Enumerable.Range(0, 60).Select(y => y.ToString("00"))));
numbers = (short*)Marshal.AllocHGlobal(numbersBytes.Length).ToPointer();
fixed (byte* n = &numbersBytes[0])
Unsafe.CopyBlock(numbers, n, (uint)numbersBytes.Length);
var templateBytes = UTF8.GetBytes("ddd, dd MMM yyyy HH:mm:ss GMT");
template = (byte*)Marshal.AllocHGlobal(templateBytes.Length).ToPointer();
fixed (byte* t = &templateBytes[0])
Unsafe.CopyBlock(template, t, (uint)templateBytes.Length);
}
public static unsafe string ToRfc1123String(DateTimeOffset dateTime)
{
byte* buf = stackalloc byte[29];
ToRfc1123String(dateTime, buf, 0);
return UTF8.GetString(buf, 29);
}
public static unsafe void ToRfc1123String(DateTimeOffset dto, byte* target, int offset)
{
var dateTime = dto.UtcDateTime;
((decimal*)target)[0] = ((decimal*)template)[0];
((long*)target)[2] = ((long*)template)[2];
((int*)target)[6] = ((int*)template)[6];
target[28] = template[28];
short* targetAsShort = (short*)target;
//https://github.com/dotnet/coreclr/blob/61cb42ceb8f6f542606c7863c7e26edea7b9653c/src/mscorlib/src/System/DateTime.cs#L742
long ticks = dateTime.Ticks;
// n = number of days since 1/1/0001
int n = (int)(ticks / TicksPerDay);
// y400 = number of whole 400-year periods since 1/1/0001
int y400 = n / DaysPer400Years;
// n = day number within 400-year period
n -= y400 * DaysPer400Years;
// y100 = number of whole 100-year periods within 400-year period
int y100 = n / DaysPer100Years;
// Last 100-year period has an extra day, so decrement result if 4
if (y100 == 4) y100 = 3;
// n = day number within 100-year period
n -= y100 * DaysPer100Years;
// y4 = number of whole 4-year periods within 100-year period
int y4 = n / DaysPer4Years;
// n = day number within 4-year period
n -= y4 * DaysPer4Years;
// y1 = number of whole years within 4-year period
int y1 = n / DaysPerYear;
// Last year has an extra day, so decrement result if 4
if (y1 == 4) y1 = 3;
// If year was requested, compute and return it
var year = y400 * 400 + y100 * 100 + y4 * 4 + y1 + 1;
// n = day number within year
n -= y1 * DaysPerYear;
// If day-of-year was requested, return it
// Leap year calculation looks different from IsLeapYear since y1, y4,
// and y100 are relative to year 1, not year 0
bool leapYear = y1 == 3 && (y4 != 24 || y100 == 3);
int[] days = leapYear ? DaysToMonth366 : DaysToMonth365;
// All months have less than 32 days, so n >> 5 is a good conservative
// estimate for the month
int month = n >> 5 + 1;
// m = 1-based month number
while (n >= days[month])
month++;
// If month was requested, return it
// Return 1-based day-of-month
var day = n - days[month - 1] + 1;
switch (dateTime.DayOfWeek)
{
case DayOfWeek.Sunday: targetAsShort[0] = *(short*)(dayNames + (0 * 3) + 0); target[2] = dayNames[(0 * 3) + 2]; break;
case DayOfWeek.Monday: targetAsShort[0] = *(short*)(dayNames + (1 * 3) + 0); target[2] = dayNames[(1 * 3) + 2]; break;
case DayOfWeek.Tuesday: targetAsShort[0] = *(short*)(dayNames + (2 * 3) + 0); target[2] = dayNames[(2 * 3) + 2]; break;
case DayOfWeek.Wednesday: targetAsShort[0] = *(short*)(dayNames + (3 * 3) + 0); target[2] = dayNames[(3 * 3) + 2]; break;
case DayOfWeek.Thursday: targetAsShort[0] = *(short*)(dayNames + (4 * 3) + 0); target[2] = dayNames[(4 * 3) + 2]; break;
case DayOfWeek.Friday: targetAsShort[0] = *(short*)(dayNames + (5 * 3) + 0); target[2] = dayNames[(5 * 3) + 2]; break;
case DayOfWeek.Saturday: targetAsShort[0] = *(short*)(dayNames + (6 * 3) + 0); target[2] = dayNames[(6 * 3) + 2]; break;
}
*(short*)(target + 5) = numbers[day];
switch (month)
{
case 1: targetAsShort[4] = *(short*)(months + (0 * 3) + 0); target[10] = months[(0 * 3) + 2]; break;
case 2: targetAsShort[4] = *(short*)(months + (1 * 3) + 0); target[10] = months[(1 * 3) + 2]; break;
case 3: targetAsShort[4] = *(short*)(months + (2 * 3) + 0); target[10] = months[(2 * 3) + 2]; break;
case 4: targetAsShort[4] = *(short*)(months + (3 * 3) + 0); target[10] = months[(3 * 3) + 2]; break;
case 5: targetAsShort[4] = *(short*)(months + (4 * 3) + 0); target[10] = months[(4 * 3) + 2]; break;
case 6: targetAsShort[4] = *(short*)(months + (5 * 3) + 0); target[10] = months[(5 * 3) + 2]; break;
case 7: targetAsShort[4] = *(short*)(months + (6 * 3) + 0); target[10] = months[(6 * 3) + 2]; break;
case 8: targetAsShort[4] = *(short*)(months + (7 * 3) + 0); target[10] = months[(7 * 3) + 2]; break;
case 9: targetAsShort[4] = *(short*)(months + (8 * 3) + 0); target[10] = months[(8 * 3) + 2]; break;
case 10: targetAsShort[4] = *(short*)(months + (9 * 3) + 0); target[10] = months[(9 * 3) + 2]; break;
case 11: targetAsShort[4] = *(short*)(months + (10 * 3) + 0); target[10] = months[(10 * 3) + 2]; break;
case 12: targetAsShort[4] = *(short*)(months + (11 * 3) + 0); target[10] = months[(11 * 3) + 2]; break;
}
if (year < 2050 && year > 2000)
((int*)target)[3] = years[(year - 2000)];
else
{
var bytes = UTF8.GetBytes(year.ToString("0000"));
target[12] = bytes[0];
target[13] = bytes[1];
target[14] = bytes[2];
target[15] = bytes[3];
}
*(short*)(target + 17) = numbers[dateTime.Hour];
*(short*)(target + 20) = numbers[dateTime.Minute];
*(short*)(target + 23) = numbers[dateTime.Second];
}
}
internal static class DateTimeFormatterKristian
{
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);
}
}
internal struct UtcOffset
{
public DateTimeOffset DateTimeOffset { get; private set; }
public long Ticks { get; private set; }
public int Day { get; private set; }
public int DayOfWeek { get; private set; }
public int Month { get; private set; }
public int Year { get; private set; }
public UtcOffset(DateTimeOffset dateTimeOffset)
{
DateTimeOffset = dateTimeOffset;
Ticks = dateTimeOffset.Date.Ticks;
Day = dateTimeOffset.Day;
DayOfWeek = (int)dateTimeOffset.DayOfWeek;
Month = dateTimeOffset.Month;
Year = dateTimeOffset.Year;
}
}
internal static class DateTimeFormatterTore
{
private static readonly DateTimeFormatInfo FormatInfo = CultureInfo.InvariantCulture.DateTimeFormat;
private static readonly string[] DayNames = FormatInfo.AbbreviatedDayNames;
private static readonly string[] MonthNames = FormatInfo.AbbreviatedMonthNames;
private static UtcOffset _utcOffset = new UtcOffset(DateTimeOffset.UtcNow);
// 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 string Gmt = "GMT";
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 utcNow = DateTimeOffset.UtcNow;
if (dateTime.Offset > TimeSpan.Zero)
dateTime = dateTime.ToUniversalTime();
if (_utcOffset.Ticks != utcNow.Date.Ticks)
_utcOffset = new UtcOffset(utcNow);
var utcOffset = _utcOffset;
if (utcOffset.Ticks != dateTime.Date.Ticks)
utcOffset = new UtcOffset(dateTime);
FormatDayOfWeek(utcOffset.DayOfWeek, target, ref offset);
Format(Comma, target, ref offset);
Format(Space, target, ref offset);
FormatNumber(utcOffset.Day, target, ref offset);
Format(Space, target, ref offset);
FormatMonth(utcOffset.Month, target, ref offset);
Format(Space, target, ref offset);
FormatYear(utcOffset.Year, target, ref offset);
Format(Space, target, ref offset);
FormatTimeOfDay(dateTime.Ticks - utcOffset.Ticks, target, ref offset);
Format(Space, target, ref offset);
Format(Gmt, target, ref offset);
return new string(target, 0, offset);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static unsafe void FormatDayOfWeek(int dayOfWeek, char* target, ref int offset)
{
Format(DayNames[dayOfWeek], target, ref offset);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static unsafe void FormatMonth(int month, char* target, ref int offset)
{
Format(MonthNames[month - 1], target, ref offset);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static unsafe void FormatYear(int year, char* target, ref int offset)
{
Format(GetAsciiChar(year / 1000), target, ref offset);
Format(GetAsciiChar(year % 1000 / 100), target, ref offset);
Format(GetAsciiChar(year % 100 / 10), target, ref offset);
Format(GetAsciiChar(year % 10), target, ref offset);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static unsafe void FormatTimeOfDay(long ticks, char* target, ref int offset)
{
var hours = ticks / TimeSpan.TicksPerHour;
var minutes = ticks % TimeSpan.TicksPerHour / TimeSpan.TicksPerMinute;
var seconds = ticks % TimeSpan.TicksPerMinute / TimeSpan.TicksPerSecond;
FormatNumber((int)hours, target, ref offset);
Format(Colon, target, ref offset);
FormatNumber((int)minutes, target, ref offset);
Format(Colon, target, ref offset);
FormatNumber((int)seconds, target, ref offset);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static unsafe void FormatNumber(int number, char* target, ref int offset)
{
Format(GetAsciiChar(number / 10), target, ref offset);
Format(GetAsciiChar(number % 10), target, ref offset);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static unsafe void Format(string source, char* target, ref int offset)
{
for (var i = 0; i < source.Length; i++)
{
target[offset++] = source[i];
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static unsafe void Format(char value, char* target, ref int offset)
{
target[offset++] = value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static char GetAsciiChar(int value)
{
return (char)(AsciiNumberOffset + value);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment