Recently I was working on a custom transport and at some point I realized my payload had grown to an unacceptable size. After some analysis I discovered that DateTime structures represented the bulk of the problem. On reflection this was completely obvious since the default binary representation of a DateTime is a long (e.g. 8 bytes). This allows us to store date representations across just over 10,000 years.
| Normal 8-Byte DateTime: |
~10005 Years |
In my case; and probably many of yours; I don't need to work with such a wide range of dates. This means I can shrink my dates by half and still have a sufficient range of dates to meet the needs of most applications.
| 4-Byte DateTime: |
~136 Years |
To do this, we can take a lesson from the POSIX community which has been converting Unix Time to and from other formats for a long time. This is a simple pattern that allows us to reuse the built-in DateTime structures. What we need are methods to convert to and from our offset 4 byte representation.
public static class TimeOffsetExtensions
{
static readonly DateTime _timeReference = new DateTime(2010, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
public static DateTime ToDateTime(this int timestamp)
{
return _timeReference.AddSeconds(timestamp);
}
public static uint ToOffsetTimestamp(this DateTime instance)
{
TimeSpan diff = instance - _timeReference;
return (uint)Math.Floor(diff.TotalSeconds);
}
}
One of the keys to this is setting a common time offset. Above you will see that I set my offset to start at 01/01/2010. As long as this never changes, then you can adjust this as needed to meet your particular requirements. With these methods in place we can apply them in reading/writing to transports, stores, or whatever. Here is an example of writing a DateTime in its short form:
DateTime time = DateTime.UtcNow;
byte[] offsetDateBytes;
using (var stream = new MemoryStream())
{
using (var writer = new BinaryWriter(stream))
{
writer.Write(time.ToOffsetTimestamp());
}
offsetDateBytes = stream.ToArray();
}
Here is an example of reading the offset bytes back into a normal DateTime object:
DateTime recoveredDateTime;
using (var stream = new MemoryStream(offsetDateBytes))
{
using (var reader = new BinaryReader(stream))
{
recoveredDateTime = reader.ReadInt32().ToDateTime();
}
}
That's all there is to it... Apply this in good health.
Tags: performance, design patterns