解剖SQLSERVER 第四篇 ?OrcaMDF里對dates類型數據的解析(譯)
http://improve.dk/parsing-dates-in-orcamdf/
在SQLSERVER里面有幾種不同的date相關類型,當前OrcaMDF 支持三種最常用的date類型:date,datetime,smalldatetime
?
SqlDate實現
date 類型在三種類型之中是最簡單的,他是一個3個字節(jié)的定長類型,存儲了日期值它支持的日期范圍從0001-01-01到9999-12-31
默認值是1900-01-01
比較坑爹的是.NET里面還沒有任何標準實現能夠支持3個字節(jié)的整數類型,只有short類型和int類型,但是,他們要不太大要不太小
另外,要正確讀取日期值,對于.NET的4字節(jié)整型我們必須執(zhí)行一些轉變去獲取正確的數字
一旦我們獲取到date的值,我們可以創(chuàng)建一個默認的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字節(jié)定長數據值,第一部分是time(4字節(jié)),而第二部分是date(4字節(jié))
計算date部分跟上面介紹date類型基本上一樣,不過這一次date部分是一個四字節(jié)整數,比上面的例子容易處理多了,上面的date類型是3個字節(jié)
time部分存儲為自午夜時的ticks數,一個tick就是1/300th 秒,為了顯示tick值,我們首先定義一個常量,常量值是10d/3d
time的各個部分實際同樣存儲在同一個整型值里面(比如時間,分鐘,秒,毫秒),所以我們要獨立訪問這些單獨的部分,我們必須
要執(zhí)行一些轉換 (包括取模和相除)
部分 計算 小時 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)并且他能精確到秒
大多數場景下,精確到秒已經足夠了,在一個范圍的時間間隔內和精確值不需要太精確的情況下會節(jié)省很多空間
smalldatetime 數據類型會只占用4個字節(jié),前2個字節(jié)存儲自午夜的分鐘數,后2個字節(jié)存儲日期,默認值是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 ])); } } }
?
第四篇完
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
