解剖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
]));
}
}
}
第四篇完

