Microsoft MVP성태의 닷넷 이야기
.NET Framework: 236. SqlDbType - DateTime, DateTime2, DateTimeOffset의 차이점 [링크 복사], [링크+제목 복사],
조회: 21232
글쓴 사람
정성태 (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

비밀번호

댓글 작성자
 




... 16  17  18  19  20  21  22  23  24  25  26  27  28  29  [30]  ...
NoWriterDateCnt.TitleFile(s)
12903정성태1/7/20227643.NET Framework: 1130. C# - ELEMENT_TYPE_INTERNAL 유형의 사용 예
12902정성태1/7/20227682오류 유형: 779. SQL 서버 로그인 에러 - provider: Shared Memory Provider, error: 0 - No process is on the other end of the pipe.
12901정성태1/5/20227729오류 유형: 778. C# - .NET 5+에서 warning CA1416: This call site is reachable on all platforms. '...' is only supported on: 'windows' 경고 발생
12900정성태1/5/20229381개발 환경 구성: 622. vcpkg로 ffmpeg를 빌드하는 경우 생성될 구성 요소 제어하는 방법
12899정성태1/3/20228895개발 환경 구성: 621. windbg에서 python 스크립트 실행하는 방법 - pykd (2)
12898정성태1/2/20229487.NET Framework: 1129. C# - ffmpeg(FFmpeg.AutoGen)를 이용한 비디오 인코딩 예제(encode_video.c) [1]파일 다운로드1
12897정성태1/2/20228298.NET Framework: 1128. C# - 화면 캡처한 이미지를 ffmpeg(FFmpeg.AutoGen)로 동영상 처리 [4]파일 다운로드1
12896정성태1/1/202211276.NET Framework: 1127. C# - FFmpeg.AutoGen 라이브러리를 이용한 기본 프로젝트 구성파일 다운로드1
12895정성태12/31/20219650.NET Framework: 1126. C# - snagit처럼 화면 캡처를 연속으로 수행해 동영상 제작 [1]파일 다운로드1
12894정성태12/30/20217623.NET Framework: 1125. C# - DefaultObjectPool<T>의 IDisposable 개체에 대한 풀링 문제 [3]파일 다운로드1
12893정성태12/27/20219297.NET Framework: 1124. C# - .NET Platform Extension의 ObjectPool<T> 사용법 소개파일 다운로드1
12892정성태12/26/20217208기타: 83. unsigned 형의 이전 값이 최댓값을 넘어 0을 지난 경우, 값의 차이를 계산하는 방법
12891정성태12/23/20217117스크립트: 38. 파이썬 - uwsgi의 --master 옵션
12890정성태12/23/20217292VC++: 152. Golang - (문자가 아닌) 바이트 위치를 반환하는 strings.IndexRune 함수
12889정성태12/22/20219768.NET Framework: 1123. C# - (SharpDX + DXGI) 화면 캡처한 이미지를 빠르게 JPG로 변환하는 방법파일 다운로드1
12888정성태12/21/20217832.NET Framework: 1122. C# - ImageCodecInfo 사용 시 System.Drawing.Image와 System.Drawing.Bitmap에 따른 Save 성능 차이파일 다운로드1
12887정성태12/21/20219975오류 유형: 777. OpenCVSharp4를 사용한 프로그램 실행 시 "The type initializer for 'OpenCvSharp.Internal.NativeMethods' threw an exception." 예외 발생
12886정성태12/20/20217748스크립트: 37. 파이썬 - uwsgi의 --enable-threads 옵션 [2]
12885정성태12/20/20218011오류 유형: 776. uwsgi-plugin-python3 환경에서 MySQLdb 사용 환경
12884정성태12/20/20217039개발 환경 구성: 620. Windows 10+에서 WMI root/Microsoft/Windows/WindowsUpdate 네임스페이스 제거
12883정성태12/19/20217988오류 유형: 775. uwsgi-plugin-python3 환경에서 "ModuleNotFoundError: No module named 'django'" 오류 발생
12882정성태12/18/20217101개발 환경 구성: 619. Windows Server에서 WSL을 위한 리눅스 배포본을 설치하는 방법
12881정성태12/17/20217528개발 환경 구성: 618. WSL Ubuntu 20.04에서 파이썬을 위한 uwsgi 설치 방법 (2)
12880정성태12/16/20217423VS.NET IDE: 170. Visual Studio에서 .NET Core/5+ 역어셈블 소스코드 확인하는 방법
12879정성태12/16/202113719오류 유형: 774. Windows Server 2022 + docker desktop 설치 시 WSL 2로 선택한 경우 "Failed to deploy distro docker-desktop to ..." 오류 발생
12878정성태12/15/20218724개발 환경 구성: 617. 윈도우 WSL 환경에서 같은 종류의 리눅스를 다중으로 설치하는 방법
... 16  17  18  19  20  21  22  23  24  25  26  27  28  29  [30]  ...