Microsoft MVP성태의 닷넷 이야기
.NET Framework: 236. SqlDbType - DateTime, DateTime2, DateTimeOffset의 차이점 [링크 복사], [링크+제목 복사],
조회: 28811
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
(연관된 글이 1개 있습니다.)

SqlDbType - DateTime, DateTime2, DateTimeOffset의 차이점

SQL 서버 입장이 아닌, 닷넷 개발자 입장에서의 차이점을 알아볼까요?

예제 DB 에 다음과 같은 테이블을 정의하고,

CREATE TABLE [dbo].[TestTable](
    [Id] [int] NOT NULL,
    [Time1] [datetime] NULL,
    [Time2] [datetime2](7) NULL,
    [TimeOffset] [datetimeoffset](7) NULL
) ON [PRIMARY]

테스트를 위해 다음과 같은 예제 코드를 작성합니다.

using (SqlConnection sqlConnection =
    new SqlConnection(@"Integrated Security=SSPI;Persist Security Info=False;Initial Catalog=TestDB;Data Source=."))
{
    sqlConnection.Open();

    // Delete
    SqlCommand deleteCommand = new SqlCommand();
    deleteCommand.Connection = sqlConnection;
    deleteCommand.CommandText = "DELETE FROM TESTTABLE";

    deleteCommand.ExecuteNonQuery();

    // Create
    SqlCommand insertCommand = new SqlCommand();
    insertCommand.Connection = sqlConnection;
    insertCommand.CommandText = "INSERT INTO TESTTABLE(Id, Time1, Time2, TimeOffset) VALUES (@Id, @Time1, @Time2, @TimeOffset)";

    insertCommand.Parameters.Add("@Id", SqlDbType.Int);
    insertCommand.Parameters.Add("@Time1", SqlDbType.DateTime);
    insertCommand.Parameters.Add("@Time2", SqlDbType.DateTime2);
    insertCommand.Parameters.Add("@TimeOffset", SqlDbType.DateTimeOffset);

    insertCommand.Parameters[0].Value = Guid.NewGuid().GetHashCode();

    DateTime now = DateTime.UtcNow;
    insertCommand.Parameters[1].Value = now;
    insertCommand.Parameters[2].Value = now;
    insertCommand.Parameters[3].Value = now;

    int affected = insertCommand.ExecuteNonQuery();
    Console.WriteLine("Now - Ticks: " + now.Ticks + ", Kind = " + now.Kind);

    // Select - DataTable
    DataSet ds = new DataSet();
    SqlDataAdapter da = new SqlDataAdapter("SELECT * FROM TESTTABLE", sqlConnection);
    da.Fill(ds, "testTable");

    DataTable dt = ds.Tables["testTable"];
    System.DateTime Time1= System.DateTime.MinValue;
    System.DateTime Time2 = System.DateTime.MinValue;
    System.DateTimeOffset Time3 = System.DateTimeOffset.MinValue;

    unsafe
    {
        Console.WriteLine("Time1/Time2 size: " + sizeof(System.DateTime));
        Console.WriteLine("TimeOffset size: " + sizeof(System.DateTimeOffset));
    }

    foreach (DataRow dr in dt.Rows)
    {
        int id = Convert.ToInt32(dr["Id"]);
        Time1 = (System.DateTime)(dr["Time1"]);
        Time2 = (System.DateTime)(dr["Time2"]);
        Time3 = (System.DateTimeOffset)(dr["TimeOffset"]);

        Console.WriteLine(string.Format("Ticks = {0}, Kind = {1}", Time1.Ticks, Time1.Kind));
        Console.WriteLine(string.Format("Ticks = {0}, Kind = {1}", Time2.Ticks, Time2.Kind));
        Console.WriteLine(string.Format("Ticks = {0}, Kind = {1}", Time3.Ticks, Time3.Offset));
    }
}

실행해 보면, 출력 결과는 다음과 같습니다.

Now - Ticks: 634491664775545133, Kind = Utc
Time1/Time2 size: 8
TimeOffset size: 12
Ticks = 634491664775530000, Kind = Unspecified
Ticks = 634491664775545133, Kind = Unspecified
Ticks = 634491664775545133, Kind = 00:00:00

차이점이 잘 드러나고 있지요!

DateTime, DateTime2가 Timezone에 대한 정보를 보관하고 있지 않기 때문에 만약 이에 대한 민감한 프로그램을 개발하고 있다면 DateTimeOffset으로 지정하는 것이 좋습니다.

DateTime의 경우 DateTime2/DateTimeOffset과의 주요 차이점은 바로 3.33...ms(밀리세컨드) 단위 까지만 데이터를 보관한다는 점이고.

닷넷 개발자 입장에서는 저 정도만 알아두면 개발하는 데 크게 무리는 없겠지만, SQL 서버상의 차이점까지 알아두시면 좀 더 도움이 될 수 있겠지요? ^^

[The ultimate guide to the datetime datatypes에서 인용]
  • DateTime
    • 8byte
    • 3.33ms 정확성
    • 1753-01-01 ~ 9999-12-31일까지의 날짜 표현

  • DateTime2
    • 6~8byte
    • 100ns 정확성
    • 0001-01-01 ~ 9999-12-31 일까지의 날짜 표현

  • DateTimeOffset
    • 8~10byte
    • 100ns 정확성
    • 0001-01-01 ~ 9999-12-31 일까지의 날짜 표현, Timezone 표현 가능


(근데 위의 표를 보니... 한 가지 이해 안되는 것이 있는데요. DateTime과 DateTime2는 점유 공간 면에서 동일하게 8바이트가 소모되는데 어째서 정확성도 떨어지고 표현이 가능한 시작 년도도 1753년으로 해야만 했을까요?)




요즘 들어서, 국제화가 중요해지다 보니 Timezone에 대한 염두를 하지 않을 수 없습니다. 그런데, 이를 고려하기 위해 DateTimeOffset을 사용하게 되면 닷넷 프로그래밍에서 DateTime 자료형이 아니게 되어 처리가 다소 번거로운 단점이 있습니다. 따라서, SQL Server에서는 무조건 데이터를 UTC 형식으로 저장한다는 원칙을 세운다면 이후의 프로그램에서 좀 더 쉽게 접근하는 것이 가능합니다.

그런데, DateTime/DateTime2에 대해서 애당초 Kind 속성을 Unspecified가 아닌 Utc로 SELECT 단계에서부터 반환받고 싶다면 어떻게 해야 할까요?

음... 방법이 없습니다. ^^; 다음과 같이 한번 더 DateTime 타입을 생성해 주는 것으로 부가적인 처리를 해주어야 합니다.

foreach (DataRow dr in dt.Rows)
{
    ...[생략]...
    System.DateTime time2 = (System.DateTime)(dr["Time2"]);
    System.DateTime time2_with_zone = new DateTime(time2.Ticks, DateTimeKind.Utc);
    ...[생략]...
}




여기서 잠깐, Firebird의 Timestamp도 짚고 넘어가볼까요? ^^

테스트를 위해 간단하게 다음과 같은 형식의 DB/Table을 만들고,

create database 'c:\temp\test.fdb' user 'SYSDBA' password 'masterkey';
commit;
connect 'c:\temp\test.fdb' user 'SYSDBA' password 'masterkey';
CREATE TABLE TESTTABLE (NAME VARCHAR(50) NOT NULL PRIMARY KEY, Created TIMESTAMP NOT NULL);

UTC 형식의 DateTime 값을 넣고 출력하는 예제를 만들어 봅니다.

using (FbConnection fbConnection =
    new FbConnection(@"DataSource=localhost;Port=3050;Database=c:\temp\test.fdb;User=SYSDBA;Password=masterkey"))
{
    fbConnection.Open();

    // Create
    FbCommand insertCommand = new FbCommand();
    insertCommand.Connection = fbConnection;
    insertCommand.CommandText = "INSERT INTO TESTTABLE(NAME, Created) VALUES (@NAME, @Created)";

    insertCommand.Parameters.Add("@NAME", FbDbType.VarChar, 50);
    insertCommand.Parameters.Add("@Created", FbDbType.TimeStamp);

    string nameValue = "Name" + Guid.NewGuid().ToString(); ;
    insertCommand.Parameters[0].Value = nameValue;
    insertCommand.Parameters[1].Value = DateTime.UtcNow;

    int affected = insertCommand.ExecuteNonQuery();
    Console.WriteLine("# of affected row: " + affected);

    // Select - DataTable
    DataSet ds = new DataSet();
    FbDataAdapter da = new FbDataAdapter("SELECT * FROM TESTTABLE", fbConnection);
    da.Fill(ds, "testTable");

    DataTable dt = ds.Tables["testTable"];
    foreach (DataRow dr in dt.Rows)
    {
        string name = dr["NAME"] as string;
        DateTime created = Convert.ToDateTime(dr["Created"]);

        Console.WriteLine(string.Format("Kind = {0}, Ticks = {1}", created.Kind, created.Ticks));
    }
}

실행 후 출력 결과는 다음과 같습니다.

[실행 예]

Kind = Unspecified, Ticks = 634491803025870000

척 보니... SQL 서버의 DateTime 타입과 동일한 결과를 보여주고 있습니다. Timezone에 대한 기록 방법이 없으니, 역시 무조건 UTC 기준으로 시간 관리를 하시는 것이 좋습니다.

첨부된 파일은 위의 코드를 포함한 예제 프로젝트입니다.




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]






[최초 등록일: ]
[최종 수정일: 7/10/2021]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 




... 181  182  183  [184]  185  186  187  188  189  190  191  192  193  194  195  ...
NoWriterDateCnt.TitleFile(s)
390정성태11/6/200626842    답변글 .NET Framework: 74.9. WCF에 SSL 적용 (2) - 서비스 제작파일 다운로드1
356정성태10/7/200622397COM 개체 관련: 19. COM의 Apartment를 이해해 보자. [8]
386light10/30/200617372    답변글 COM 개체 관련: 19.1. [답변]: COM 객체를 글로벌마샬으로 만든후, 사용한다.
355정성태10/9/200625140개발 환경 구성: 19. Internet_Zone 하위에 새로운 코드 그룹을 추가하는 예제 [4]파일 다운로드2
353정성태12/31/200633430개발 환경 구성: 18. 윈도우즈 인증서 서비스 이야기 [3]
354정성태10/23/200635958    답변글 개발 환경 구성: 18.1. 윈도우즈 인증서 서비스 설치
372정성태12/31/200637853    답변글 개발 환경 구성: 18.2. 웹 사이트에 SSL을 적용 [3]
373정성태10/24/200629250    답변글 개발 환경 구성: 18.3. 사용자 입장에서의 HTTPS 접근 (1)
374정성태10/25/200626512    답변글 개발 환경 구성: 18.4. 사용자 입장에서의 HTTPS 접근 (2)
391정성태11/7/200630636    답변글 개발 환경 구성: 18.5. 사용자 인증서 발급
392정성태11/11/200643833    답변글 개발 환경 구성: 18.6. 인증서 관리 (1) - 내보내기/가져오기
394정성태11/9/200628338    답변글 개발 환경 구성: 18.7. 인증서 관리 (2) - 개인키를 내보낼 수 있는 유형의 인증서 발급 [1]
395정성태11/9/200640451    답변글 개발 환경 구성: 18.8. 인증서 관리 (3) - 인증서 MMC 관리자 사용
414정성태12/23/200632159    답변글 개발 환경 구성: 18.9. CRL(Certificate Revocation List) 관리
428정성태12/31/200645028    답변글 개발 환경 구성: 18.10. IIS 7 - SSL 사이트 설정하는 방법 [4]
429정성태12/31/200631046    답변글 개발 환경 구성: 18.11. 서비스를 위한 인증서 설치
352정성태10/2/200620766개발 환경 구성: 17. VPC에 Linux 설치하는 방법 [1]
351정성태10/8/200623316개발 환경 구성: 16. 성태의 무식한(!) 리눅스 탐방기. [4]
349정성태9/26/200621994디버깅 기술: 10. C++/CLI에서 제공되는 명시적인 파괴자의 비밀
347정성태10/6/200625747디버깅 기술: 9. .NET IDisposable 처리 정리 [1]
346정성태9/23/200619260개발 환경 구성: 15. 툴박스에 컨트롤이 자동으로 나타나도록 해주는 옵션 설정
345정성태9/20/200618468오류 유형: 12. WCF 오류 메시지 - Error while trying to reflect on attribute 'MessageContractAttribute'
343정성태10/18/200630327개발 환경 구성: 14. SandCastle 사용법 (NDoc을 대체하는 문서화 도구) [1]파일 다운로드1
344정성태9/20/200620541    답변글 개발 환경 구성: 14.1. 오류 유형 - GAC 에 등록된 DLL 에 대한 문서화 시 오류
340정성태9/15/200619795개발 환경 구성: 13. ISO 파일을 가상 CD-ROM으로 매핑해주는 프로그램
339정성태9/14/200619278오류 유형: 11. ProtocolsSection?
... 181  182  183  [184]  185  186  187  188  189  190  191  192  193  194  195  ...