DateTime.ToString()에 "yyyy-MM-dd" 같은 사용자 지정 패턴만 쓰다 보면, 한 글자짜리 "d", "s", "o" 같은 지정자는 잘 안 쓰게 됩니다. 그런데 이 표준 지정자들은 쓰임새가 분명합니다. 화면에 보여 줄 때는 사용자의 지역 설정을 그대로 따르게 하고, 파일이나 API로 주고받을 때는 문화권과 상관없이 늘 같은 모양이 나오게 해 줍니다. 어느 쪽인지 구분해서 고르는 게 요령입니다.

지정자 한눈에 보기

아래 표의 예는 모두 2009-06-15 13:45:30 이라는 같은 값을 여러 문화권으로 출력한 결과입니다. 같은 지정자라도 문화권에 따라 순서와 언어가 완전히 달라진다는 게 보입니다.

광고
지정자 의미 출력 예
"d" 간단한 날짜 6/15/2009 (en-US), 15/06/2009 (fr-FR), 2009/06/15 (ja-JP)
"D" 자세한 날짜 Monday, June 15, 2009 (en-US), 15 июня 2009 г. (ru-RU), Montag, 15. Juni 2009 (de-DE)
"f" 전체 날짜 + 간단한 시간 Monday, June 15, 2009 1:45 PM (en-US), den 15 juni 2009 13:45 (sv-SE)
"F" 전체 날짜 + 자세한 시간 Monday, June 15, 2009 1:45:30 PM (en-US), den 15 juni 2009 13:45:30 (sv-SE)
"g" 일반 날짜 + 간단한 시간 6/15/2009 1:45 PM (en-US), 15/06/2009 13:45 (es-ES), 2009/6/15 13:45 (zh-CN)
"G" 일반 날짜 + 자세한 시간 6/15/2009 1:45:30 PM (en-US), 15/06/2009 13:45:30 (es-ES), 2009/6/15 13:45:30 (zh-CN)
"M", "m" 월/일 June 15 (en-US), 15. juni (da-DK), 15 Juni (id-ID)
"O", "o" 라운드트립 아래 별도 설명
"R", "r" RFC1123 Mon, 15 Jun 2009 20:45:30 GMT
"s" 정렬 가능한 날짜/시간 2009-06-15T13:45:30
"t" 간단한 시간 1:45 PM (en-US), 13:45 (hr-HR)
"T" 자세한 시간 1:45:30 PM (en-US), 13:45:30 (hr-HR)
"u" 정렬 가능한 유니버설 2009-06-15 13:45:30Z (DateTime), 2009-06-15 20:45:30Z (DateTimeOffset)
"U" 유니버설 전체 날짜/시간 Monday, June 15, 2009 8:45:30 PM (en-US), den 15 juni 2009 20:45:30 (sv-SE)
"Y", "y" 연도와 월 June, 2009 (en-US), juni 2009 (da-DK), Juni 2009 (id-ID)

대문자와 소문자가 다른 뜻인 경우가 많습니다(d와 D, t와 T, u와 U). 반대로 M/m, O/o, R/r, Y/y는 대소문자 어느 쪽을 써도 결과가 같습니다. 사용자 지정 패턴에서 M은 월, m은 분이라 헷갈리기 쉬운데, 한 글자만 단독으로 넘기면 표준 지정자로 해석된다는 점을 기억해 두세요.

"R"과 "U" 예에서 13:45가 20:45로 바뀐 건 원본 예시가 UTC-7 지역 시간을 기준으로 했기 때문입니다. 이 두 지정자는 출력할 때 UTC로 바꿔서 보여 줍니다.

라운드트립 "o"는 무엇이 다른가

"o"는 값을 문자열로 바꿨다가 다시 읽어 들였을 때 정보가 하나도 사라지지 않게 만든 형식입니다. 소수점 아래 7자리까지 찍고, 시간대 정보도 붙입니다. DateTime의 Kind 값에 따라 결과가 달라집니다.

DateTime 값:
2009-06-15T13:45:30 (DateTimeKind.Local)       --> 2009-06-15T13:45:30.0000000-07:00
2009-06-15T13:45:30 (DateTimeKind.Utc)         --> 2009-06-15T13:45:30.0000000Z
2009-06-15T13:45:30 (DateTimeKind.Unspecified) --> 2009-06-15T13:45:30.0000000

DateTimeOffset 값:
2009-06-15T13:45:30-07:00 --> 2009-06-15T13:45:30.0000000-07:00

"s"도 비슷하게 생겼지만 밀리초 이하와 시간대가 빠집니다. Local이든 Utc든 2009-06-15T13:45:30으로 똑같이 나와요. 그래서 시간대가 섞이는 데이터라면 "s"보다 "o"가 안전합니다.

사용 예

C#에서는 이렇게 씁니다.

Console.WriteLine(date1.ToString("d", CultureInfo.CreateSpecificCulture("en-US")));
// Displays 4/10/2008

VB.NET도 같습니다. 문화권을 지정하지 않으면 프로그램이 돌아가는 PC의 지역 설정을 따릅니다.

Imports System.Globalization

Dim dt As DateTime = DateTime.Now

' 화면 표시용: 현재 PC 지역 설정을 따름
Label1.Text = dt.ToString("D")

' 특정 문화권으로 고정
Dim us As String = dt.ToString("d", CultureInfo.CreateSpecificCulture("en-US"))

' 로그·DB·파일명용: 문화권과 무관하게 항상 같은 모양
Dim sortable As String = dt.ToString("s")
Dim roundTrip As String = dt.ToString("o")

상황별로 고르는 기준

화면에 날짜를 보여 줄 때는 "d", "D", "g" 같은 문화권 의존 지정자가 맞습니다. 사용자가 익숙한 형식으로 알아서 바뀌니까요. 대신 이 값을 그대로 DB에 문자열로 넣거나 파일에 저장하면 문제가 생깁니다. 한국어 윈도우에서 저장한 날짜를 영어 윈도우에서 DateTime.Parse로 읽으면 월과 일이 뒤바뀌거나 아예 파싱에 실패할 수 있습니다. 해외 지사 PC에 배포한 프로그램에서 날짜가 이상하게 나온다는 문의가 들어오면 대개 이 경우입니다.

로그 파일, CSV, 서버로 보내는 JSON처럼 기계가 다시 읽을 값에는 "s"나 "o", "u"를 씁니다. "s" 형식은 문자열 정렬 순서와 시간 순서가 일치해서 파일 이름 앞에 붙여 두면 탐색기에서 이름순 정렬만으로 날짜순이 됩니다. 다만 "s" 결과에는 콜론(:)이 들어가서 윈도우 파일명으로는 쓸 수 없으니, 파일명에는 "yyyyMMdd_HHmmss" 같은 사용자 지정 패턴이 낫습니다. HTTP 헤더처럼 RFC1123 형식을 요구하는 곳에는 "r"을 쓰면 별도 패턴을 외울 필요가 없습니다.

참고: 표준 날짜 및 시간 서식 문자열 - Microsoft Learn