解剖SQLSERVER 第四篇 OrcaMDF里對dates類型數據的解析(譯)
http://improve.dk/parsing-dates-in-orcamdf/
在SQLSERVER里面有幾種不同的date相關類型,當前OrcaMDF 支持三種最常用的date類型:date,datetime,smalldatetime
SqlDate實現
date 類型在三種類型之中是最簡單的,他是一個3個字節的定長類型,存儲了日期值它支持的日期范圍從0001-01-01到9999-12-31
默認值是1900-01-01
比較坑爹的是.NET里面還沒有任何標準實現能夠支持3個字節的整數類型,只有short類型和int類型,但是,他們要不太大要不太小
另外,要正確讀取日期值,對于.NET的4字節整型我們必須執行一些轉變去獲取正確的數字
一旦我們獲取到date的值,我們可以創建一個默認的datetime類型并且添加天數進去
public class SqlDate : ISqlType { public bool IsVariableLength { get { return false; } } public short? FixedLength { get { return 3; } } public object GetValue(byte[] value) { if (value.Length != 3) throw new ArgumentException("Invalid value length: " + value.Length); // Magic needed to read a 3 byte integer into .NET's 4 byte representation. // Reading backwards due to assumed little endianness. int date = (value[2] << 16) + (value[1] << 8) + value[0]; return new DateTime(1, 1, 1).AddDays(date); } }
相關測試
using System; using NUnit.Framework; using OrcaMDF.Core.Engine.SqlTypes; namespace OrcaMDF.Core.Tests.Engine.SqlTypes { [TestFixture] public class SqlDateTests { [Test] public void GetValue() { var type = new SqlDate(); var input = new byte[] { 0xf6, 0x4c, 0x0b }; Assert.AreEqual(new DateTime(2028, 09, 09), Convert.ToDateTime(type.GetValue(input))); input = new byte[] { 0x71, 0x5c, 0x0b }; Assert.AreEqual(new DateTime(2039, 07, 17), Convert.ToDateTime(type.GetValue(input))); } [Test] public void Length() { var type = new SqlDate(); Assert.Throws<ArgumentException>(() => type.GetValue(new byte[2])); Assert.Throws<ArgumentException>(() => type.GetValue(new byte[4])); } } }
SqlDateTime實現
date類型只能存儲日期,而datetime類型不但能存儲date也能存儲time
datetime存儲8字節定長數據值,第一部分是time(4字節),而第二部分是date(4字節)
計算date部分跟上面介紹date類型基本上一樣,不過這一次date部分是一個四字節整數,比上面的例子容易處理多了,上面的date類型是3個字節
time部分存儲為自午夜時的ticks數,一個tick就是1/300th 秒,為了顯示tick值,我們首先定義一個常量,常量值是10d/3d
time的各個部分實際同樣存儲在同一個整型值里面(比如時間,分鐘,秒,毫秒),所以我們要獨立訪問這些單獨的部分,我們必須
要執行一些轉換 (包括取模和相除)
部分 計算 小時 X / 300 / 60 / 60 分鐘 X / 300 / 60 % 60 秒 X / 300 % 60 毫秒 X % 300 * 10d / 3d
public class SqlDateTime : ISqlType { private const double CLOCK_TICK_MS = 10d/3d; public bool IsVariableLength { get { return false; } } public short? FixedLength { get { return 8; } } public object GetValue(byte[] value) { if (value.Length != 8) throw new ArgumentException("Invalid value length: " + value.Length); int time = BitConverter.ToInt32(value, 0); int date = BitConverter.ToInt32(value, 4); return new DateTime(1900, 1, 1, time/300/60/60, time/300/60%60, time/300%60, (int)Math.Round(time%300*CLOCK_TICK_MS)).AddDays(date); } }
相關測試
using System; using NUnit.Framework; using OrcaMDF.Core.Engine.SqlTypes; namespace OrcaMDF.Core.Tests.Engine.SqlTypes { [TestFixture] public class SqlDateTimeTests { [Test] public void GetValue() { var type = new SqlDateTime(); byte[] input; input = new byte[] { 0x5e, 0x3b, 0x5d, 0x00, 0x25, 0x91, 0x00, 0x00 }; Assert.AreEqual(new DateTime(2001, 09, 25, 05, 39, 26, 820), (DateTime)type.GetValue(input)); input = new byte[] { 0xb6, 0x87, 0xf0, 0x00, 0xd1, 0x8b, 0x00, 0x00 }; Assert.AreEqual(new DateTime(1997, 12, 31, 14, 35, 44, 607), (DateTime)type.GetValue(input)); input = new byte[] { 0x2d, 0xfd, 0x1c, 0x01, 0x4a, 0x75, 0x00, 0x00 }; Assert.AreEqual(new DateTime(1982, 03, 18, 17, 17, 36, 790), (DateTime)type.GetValue(input)); input = new byte[] { 0xff, 0x81, 0x8b, 0x01, 0x7f, 0x24, 0x2d, 0x00 }; Assert.AreEqual(new DateTime(9999, 12, 31, 23, 59, 59, 997), (DateTime)type.GetValue(input)); } [Test] public void Length() { var type = new SqlDateTime(); Assert.Throws<ArgumentException>(() => type.GetValue(new byte[9])); Assert.Throws<ArgumentException>(() => type.GetValue(new byte[7])); } } }
SqlSmallDateTime實現
Smalldatetime 是一個不錯的數據類型當你需要存儲范圍值內的日期值(1900~2079)并且他能精確到秒
大多數場景下,精確到秒已經足夠了,在一個范圍的時間間隔內和精確值不需要太精確的情況下會節省很多空間
smalldatetime 數據類型會只占用4個字節,前2個字節存儲自午夜的分鐘數,后2個字節存儲日期,默認值是1900-1-1
處理的方法跟datetime差不多,只不過使用更小的范圍
部分 計算 小時 X / 60 分鐘 X % 60
public class SqlSmallDateTime : ISqlType { public bool IsVariableLength { get { return false; } } public short? FixedLength { get { return 4; } } public object GetValue(byte[] value) { if (value.Length != 4) throw new ArgumentException("Invalid value length: " + value.Length); ushort time = BitConverter.ToUInt16(value, 0); ushort date = BitConverter.ToUInt16(value, 2); return new DateTime(1900, 1, 1, time / 60, time % 60, 0).AddDays(date); } }
相關測試
using System; using NUnit.Framework; using OrcaMDF.Core.Engine.SqlTypes; namespace OrcaMDF.Core.Tests.Engine.SqlTypes { [TestFixture] public class SqlSmallDateTimeTests { [Test] public void GetValue() { var type = new SqlSmallDateTime(); var input = new byte[] { 0xab, 0x02, 0x5d, 0x26 }; Assert.AreEqual(new DateTime(1926, 11, 22, 11, 23, 0), Convert.ToDateTime(type.GetValue(input))); input = new byte[] { 0x49, 0x03, 0x99, 0x09 }; Assert.AreEqual(new DateTime(1906, 9, 24, 14, 1, 0), Convert.ToDateTime(type.GetValue(input))); } [Test] public void Length() { var type = new SqlSmallDateTime(); Assert.Throws<ArgumentException>(() => type.GetValue(new byte[3])); Assert.Throws<ArgumentException>(() => type.GetValue(new byte[5])); } } }
第四篇完
文章列表