본문 바로가기

C#

C# DateTime.ParseExact와 TryParseExact 활용

날짜/시간 파싱은 입력 형식이 조금만 달라도 오류가 나기 쉽습니다. ParseExact/TryParseExact는 포맷을 고정해 안전하게 처리할 수 있는 가장 확실한 방법입니다.

1. ParseExact는 언제 쓰나요?

입력 형식이 명확히 정해져 있을 때 사용합니다. 예: API, 배치 파일, 로그 등. 형식이 맞지 않으면 예외가 발생하므로 신뢰성 있는 데이터만 들어올 때 적합합니다.

using System;
using System.Globalization;

string s = "2026-07-08 14:30";
DateTime dt = DateTime.ParseExact(
    s,
    "yyyy-MM-dd HH:mm",
    CultureInfo.InvariantCulture,
    DateTimeStyles.None);
// dt.Kind == Unspecified

2. TryParseExact는 언제 쓰나요?

사용자 입력처럼 실패 가능성이 있는 경우 예외 없이 true/false로 결과를 받습니다. 반복 처리(루프) 시 예외 비용을 줄여 성능에도 유리합니다.

using System;
using System.Globalization;

if (DateTime.TryParseExact(
    input,
    "yyyy-MM-dd",
    CultureInfo.InvariantCulture,
    DateTimeStyles.AllowWhiteSpaces,
    out var date))
{
    // 성공 처리
}
else
{
    // 실패 처리
}

3. 자주 쓰는 포맷 문자열과 주의점

- yyyy-MM-dd, yyyyMMdd, dd/MM/yyyy HH:mm:ss, HH:mm:ss.fff, "o"(Round-trip), "r"(RFC1123) 등입니다.
- MM(월) vs mm(분), HH(24시) vs hh(12시)+tt, yyyy(연) vs YYYY(주차 기준 연도, 잘못 사용) 등 대소문자에 특히 주의합니다.

4. Culture와 DateTimeStyles 제대로 쓰기

숫자만 있는 포맷은 InvariantCulture를 권장합니다. 시간대/UTC 처리는 DateTimeStyles로 제어합니다.

using System;
using System.Globalization;

// ISO 8601 + 시간대 처리: K 토큰 사용(Z, +09:00, 없음 허용)
string s1 = "2026-07-08T14:30:00Z";
string s2 = "2026-07-08T14:30:00+09:00";

bool ok1 = DateTime.TryParseExact(
    s1,
    "yyyy-MM-dd'T'HH:mm:ssK",
    CultureInfo.InvariantCulture,
    DateTimeStyles.AdjustToUniversal,
    out var utc1);

bool ok2 = DateTime.TryParseExact(
    s2,
    "yyyy-MM-dd'T'HH:mm:ssK",
    CultureInfo.InvariantCulture,
    DateTimeStyles.AdjustToUniversal,
    out var utc2);

// utc1.Kind == Utc, utc2.Kind == Utc

자주 쓰는 스타일: AllowWhiteSpaces(공백 허용), AssumeUniversal/AssumeLocal(시간대 미지정 시 가정), AdjustToUniversal(UTC로 변환), RoundtripKind(Kind 보존)입니다.

5. 여러 포맷을 한 번에 처리

외부 입력이 여러 포맷 중 하나일 때 배열로 전달해 안전하게 파싱합니다.

using System;
using System.Globalization;

string[] formats = { "yyyy-MM-dd", "yyyy/MM/dd", "dd.MM.yyyy" };
if (DateTime.TryParseExact(
    input,
    formats,
    CultureInfo.InvariantCulture,
    DateTimeStyles.None,
    out var date))
{
    // 매칭된 첫 포맷으로 파싱 성공
}

6. 실전 예제: ISO 8601와 로그 타임스탬프

ISO 8601, RFC3339, 서버 로그 등 실무 포맷을 정확히 다룹니다.

using System;
using System.Globalization;

// Apache/Nginx 로그 예: 08/Jul/2026:14:30:15 +0900
string logTs = "08/Jul/2026:14:30:15 +0900";
bool ok = DateTime.TryParseExact(
    logTs,
    "dd/MMM/yyyy:HH:mm:ss zzz",
    CultureInfo.InvariantCulture, // Jul 같은 영문 월 약어
    DateTimeStyles.AdjustToUniversal,
    out var tsUtc);

// Round-trip 포맷("o") 저장/복원
DateTime now = DateTime.UtcNow;
string saved = now.ToString("o", CultureInfo.InvariantCulture);
DateTime restored = DateTime.ParseExact(
    saved, "o", CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind);

7. 안정성과 성능 팁

- 실패 가능성이 있으면 TryParseExact를 기본으로 사용합니다.
- 반복 파싱 시 CultureInfo와 포맷 배열을 재사용해 GC와 할당을 줄입니다.
- .NET 6+: DateOnly/TimeOnly가 더 적합한 경우 해당 타입의 ParseExact/TryParseExact를 고려합니다.
- 고성능 입력 처리에는 ReadOnlySpan<char> 오버로드를 활용합니다.

// Span 오버로드(.NET Core 2.1+)
ReadOnlySpan<char> span = input.AsSpan();
if (DateTime.TryParseExact(
    span,
    "yyyyMMdd".AsSpan(),
    System.Globalization.CultureInfo.InvariantCulture,
    System.Globalization.DateTimeStyles.None,
    out var d))
{
    // 성공
}

8. 마무리

ParseExact/TryParseExact는 날짜/시간 파싱의 정확도를 보장하는 가장 확실한 도구입니다. 포맷, Culture, DateTimeStyles를 명시적으로 설정하면 지역/시간대 이슈와 미묘한 버그를 효과적으로 예방할 수 있습니다.