NAME

strftime - 날짜 및 시간 서식화

SYNOPSIS

#include <time.h>

size_t strftime(char *restrict s, size_t max,
                const char *restrict format,
                const struct tm *restrict tm);

DESCRIPTION

strftime() 함수는 분할 시간 tm을 서식 format에 따라 변환해서 그 결과를 크기 max인 문자 배열 s에 집어넣는다. 분할 시간 구조체 tm<time.h>에 정의돼 있다. ctime(3)도 참고.

서식은 널 종료 문자열이며 변환 지정자라는 특별한 문자 열을 포함할 수 있다. 각 변환 지정자는 '%' 문자로 시작해서 변환 지정 문자라는 어떤 다른 문자로 끝난다. 그 외의 모든 문자 열은 일반 문자 열이다.

일반 문자 열의 문자들(널 바이트 포함)은 format에서 s로 그대로 복사한다. 하지만 변환 지정자의 문자들은 아래 목록에 나온 대로 바꿔서 넣는다. 이 목록에는 tm 구조체의 이용 필드(들)도 적혀 있다.

%a 현재 로캘에 따른 요일 축약 이름. (tm_wday로 계산) (ABDAY_{1-7}을 인자로 nl_langinfo(3)를 호출해서 현재 로캘에서 쓰는 구체적 이름을 얻을 수 있다.)
%A 현재 로캘에 따른 요일 원래 이름. (tm_wday로 계산) (DAY_{1-7}을 인자로 nl_langinfo(3)를 호출해서 현재 로캘에서 쓰는 구체적 이름을 얻을 수 있다.)
%b 현재 로캘에 따른 월 축약 이름. (tm_mon으로 계산) (ABMON_{1-12}를 인자로 nl_langinfo(3)를 호출해서 현재 로캘에서 쓰는 구체적 이름을 얻을 수 있다.)
%B 현재 로캘에 따른 월 원래 이름. (tm_mon으로 계산) (MON_{1-12}를 인자로 nl_langinfo(3)를 호출해서 현재 로캘에서 쓰는 구체적 이름을 얻을 수 있다.)
%c 현재 로캘에서 많이 쓰는 날짜 및 시간 표현. (%c 변환 지정자는 D_T_FMT를, %Ec 변환 지정자는 ERA_D_T_FMT를 인자로 nl_langinfo(3)를 호출해서 현재 로캘에서 쓰는 구체적 이름을 얻을 수 있다.) (POSIX 로캘에서는 %a %b %e %H:%M:%S %Y와 같다.)
%C 2자리 정수로 된 세기 번호 (년/100). (SU) (%EC 변환 지정자는 시대 이름에 해당한다.) (tm_year로 계산)
%d 십진수로 된 월 중 날짜. (01에서 31까지) (tm_mday로 계산)
%D %m/%d/%y와 같음. (윽... 미국 전용. 다른 나라에서는 %d/%m/%y가 꽤 흔하다는 걸 미국인들은 알아 둘 필요가 있다. 즉 국제적인 맥락에서 이 형식은 명확하지 않으므로 쓰지 않는 게 좋다.) (SU)
%e %d처럼 십진수로 된 월 중 날짜이되, 앞의 0을 공백으로 바꾼 것. (SU) (tm_mday로 계산)
%E 수식자: 대체 ("시대 기반") 형식 사용. 아래 참고. (SU)
%F %Y-%m-%d와 같음. (ISO 8601 날짜 형식) (C99)
%G ISO 8601 주 기준의 세기 포함한 십진수로 된 연도. (NOTES 참고.) ISO 주 번호(%V 참고)에 부합하는 4자리 연도. %Y와 형식 및 값이 같되 ISO 주 번호가 지난해나 다음해에 속하면 그 연도를 쓴다. (TZ) (tm_year, tm_yday, tm_wday로 계산)
%g %G와 같되 세기 없이. 즉 2자리 연도. (00-99) (TZ) (tm_year, tm_yday, tm_wday로 계산)
%h %b와 같음. (SU)
%H 십진수로 된 24시간 시계 시간. (00에서 23까지) (tm_hour로 계산)
%I 신진수로 된 12시간 시계 시간. (01에서 12까지) (tm_hour로 계산)
%j 십진수로 된 연 중 날짜. (001에서 366까지) (tm_yday로 계산)
%k 십진수로 된 (24시간 시계) 시간. (0에서 23까지) 1자리 수 앞에는 공백이 붙음. (%H 참고.) (tm_hour로 계산) (TZ)
%l 십진수로 된 (12시간 시계) 시간. (1에서 12까지) 1자리 수 앞에는 공백이 붙음. (^I 참고.) (tm_hour로 계산) (TZ)
%m 십진수로 된 월. (01에서 12까지) (tm_mon으로 계산)
%M 십진수로 된 분. (00에서 59까지) (tm_min으로 계산)
%n 개행 문자. (SU)
%O 수식자: 대체 숫자 심볼 사용. 아래 참고. (SU)
%p 해당 시간 값에 따라 "AM"이나 "PM", 또는 현재 로캘의 해당 문자열. 정오는 "PM"으로 취급하고 자정은 "AM"으로 취급한다. (tm_hour로 계산) (각기 AM_STRPM_STR을 인자로 nl_langinfo(3)를 호출해서 현재 로캘에서 "AM" 및 "PM"에 대해 쓰는 구체적 문자열 표현을 얻을 수 있다.)
%P %p와 같되 소문자. 즉 "am"이나 "pm", 또는 현재 로캘의 해당 문자열. (tm_hour로 계산) (GNU)
%r 오전 오후를 표기한 시간. (SU) (T_FMT_AMPM을 인자로 nl_langinfo(3)를 호출해서 현재 로캘에서 쓰는 구체적 형식을 얻을 수 있다.) (POSIX 로캘에서는 %I:%M:%S %p와 같다.)
%R 24시간 표기법으로 된 시간 (%H:%M). (SU) 초를 포함한 버전은 아래 %T를 보라.
%s 에포크 1970-01-01 00:00:00 +0000 (UTC) 이후의 초 수. (TZ) (mktime(tm)으로 계산)
%S 십진수로 된 초. (00에서 60까지) (가끔 있는 윤초를 위해 범위가 60까지임.) (tm_sec으로 계산)
%t 탭 문자. (SU)
%T 24시간 표기법으로 된 시간 (%H:%M:%S). (SU)
%u 십진수로 된 주 중 날 번호. 1에서 7까지이며 월요일이 1. %w 참고. (tm_wday로 계산) (SU)
%U 십진수로 된 당해 중 주 번호. 00에서 53까지이며, 첫 번째 일요일을 01번 주 첫날로 삼는다. %V%W도 참고. (tm_ydaytm_wday로 계산)
%V 십진수로 된 ISO 8601 방식 당해 중 주 번호. (아래 참고.) 01에서 53까지이며, 최소 4일이 새해에 있는 첫 번째 주가 1번 주이다. %U%W도 참고. (tm_year, tm_yday, tm_wday로 계산) (SU)
%w 십진수로 된 주 중 날 번호. 0에서 6까지이며 일요일이 0. %u 참고. (tm_wday로 계산)
%W 십진수로 된 당해 중 주 번호. 00에서 53까지이며, 첫 번째 월요일을 01번 주 첫날로 삼는다. (tm_ydaytm_wday로 계산)
%x 현재 로캘에서 많이 쓰는 시간 없는 날짜 표현. (%x 변환 지정자는 D_FMT를, %Ex 변환 지정자는 ERA_D_FMT를 인자로 nl_langinfo(3)를 호출해서 현재 로캘에서 쓰는 구체적 형식을 얻을 수 있다.) (POSIX 로캘에서는 %m/%d/%y와 같다.)
%X 현재 로캘에서 많이 쓰는 날짜 없는 시간 표현. (%X 변환 지정자는 T_FMT를, %EX 변환 지정하는 ERA_T_FMT를 인자로 nl_langinfo(3)를 호출해서 현재 로캘에서 쓰는 구체적 형식을 얻을 수 있다.) (POSIX 로캘에서는 %H:%M:%S와 같다.)
%y 세기 없이 십진수로 된 연도. (00에서 99까지) (%Ey 변환 지정자는 %EC 변환 지정자가 나타내는 시대 시작점부터의 연도에 해당한다.) (tm_year로 계산)
%Y 세기 포함한 십진수로 된 연도. (%EY 변환 지정자는 완전한 대체 연도 표현 방식에 해당한다.) (tm_year로 계산)
%z +hhmm 또는 -hhmm 숫자 형식 시간대 (즉 UTC와의 시간 및 분 차이). (SU)
%Z 시간대 이름 또는 축약명.
%+ date(1) 형식의 날짜 및 시간. (TZ) (glibc2에서 지원하지 않음.)
%% '%' 문자 자체.

몇몇 변환 지정자들에선 변환 지정 문자 앞에 EO 수식자를 붙여서 대체 형식을 써야 한다고 표시할 수 있다. 현재 로캘에서 대체 형식 내지 지정자가 존재하지 않는 경우에는 수식 없이 변환 지정자를 쓴 것처럼 동작하게 된다. (SU) 단일 유닉스 규격에서는 %Ec, %EC, %Ex, %EX, %Ey, %EY, %Od, %Oe, %OH, %OI, %Om, %OM, %OS, %Ou, %OU, %OV, %Ow, %OW, %Oy를 언급하는데, O 수식자의 효과는 대체 숫자 심볼(가령 로마 숫자)을 쓰는 것이고 E 수식자의 효과는 로캘별 대체 표현을 쓰는 것이다. ERAnl_langinfo(3)에 인자로 주어 E 수식자 있는 날짜 표현을 규정하는 규칙을 얻을 수 있다. 그런 대체 형식의 한 예가 ja_JP glibc 로캘의 일본 시대 역법 체계이다.

RETURN VALUE

결과 문자열이 종료 널 바이트를 포함해서 max 바이트를 초과하지 않는 경우에는 배열 s에 집어넣은 (종료 널 바이트를 뺀) 바이트 수를 strftime()이 반환한다. 결과 문자열의 (종료 널 바이트 포함) 길이가 max 바이트를 넘게 될 경우에는 strftime()이 0을 반환하며, 이때 배열의 내용물은 규정돼 있지 않다.

참고로 반환 값 0이 반드시 오류를 나타내는 건 아니다. 예를 들어 여러 로캘에서 %p는 빈 문자열이 된다. format 문자열이 비어 있어도 마찬가지로 빈 문자열이 나온다.

ENVIRONMENT

환경 변수 TZLC_TIME을 쓴다.

ATTRIBUTES

이 절에서 사용하는 용어들에 대한 설명은 attributes(7)를 보라.

인터페이스 속성
strftime() 스레드 안전성 MT-Safe env locale

CONFORMING TO

SVr4, C89, C99. ANSI C에 있는 변환(표시 없음), 단일 유닉스 규격에 있는 변환(SU 표시), Olson의 timezone 패키지에서 제공하는 변환(TZ 표시), glibc에서 제공하는 변환(GNU 표시) 사이에는 엄격한 포함 관계가 있다. 단 glibc2에서는 %+를 지원하지 않는다. 한편으로 glibc2에는 다른 여러 확장이 있다. POSIX.1에서는 ANSI C만을 참조한다. 하지만 POSIX.2에서는 date(1) 하에서 strftime()에도 적용될 수 있을 여러 확장들을 기술한다. %F 변환은 C99 및 POSIX.1-2001에 있다.

SUSv2에서는 %S 변환자의 허용 범위가 00에서 61까지이다. 이는 1분에 윤초가 두 번 포함될 수 있는 이론적 가능성에 대비하기 위한 것이다. (지금까지 그런 경우는 한 번도 없었다.)

NOTES

ISO 8601 주 날짜

%G, %g, %V는 ISO 8601 표준에 정의된 주 기준 연도로부터 계산한 값들을 내놓는다. 이 체계에서는 월요일부터 한 주가 시작하며, 첫 번째 주에 01번을 붙이고 마지막 주는 52번이나 53번이 된다. 1번 주는 4일 이상이 새해에 속하는 첫 번째 주이다. (즉 01번 주는 목요일을 포함한 그해 첫째 주이기도 하고, 1월 4일을 포함한 주이기도 하다.) 새해의 달력 첫 번째 주에서 3일 이하가 그해에 속할 때 ISO 8601 주 기반 체계에서는 그 날들을 전해의 52번 내지 53번 주로 계산한다. 예를 들어 2010년 1월 1일은 금요일이고, 그래서 그 달력 주의 3일만 2010년에 속한다. 따라서 ISO 8601 주 기반 체계에서는 그 날들을 2009년(%G)의 53번 주(%V)에 포함되는 걸로 본다. 즉 ISO 8601 기준 2010년의 01번 주는 2010년 1월 4일 월요일에 시작한다. 마찬가지로 2011년 1월의 처음 이틀은 2010년의 52번 주에 포함되는 걸로 본다.

glibc 참고 사항

glibc에서는 변환 지정자 확장을 몇 가지 제공한다. (이 확장들은 POSIX.1-2001에 명세돼 있지 않지만 몇몇 다른 시스템에서도 비슷한 기능을 제공한다.) '%' 문자와 변환 지정 문자 사이에 선택적으로 플래그와 필드 폭을 지정할 수 있다. (EO 수식자가 있는 경우에는 그 앞에 온다.)

다음 플래그 문자들을 허용한다.

_ (밑줄) 수로 된 결과 문자열의 남는 공간을 공백 문자로 채운다.
- (대시) 수로 된 결과 문자열의 남는 공간을 채우지 않는다.
0 수로 된 결과 문자열의 남는 공간을 0으로 채운다. 그 변환 지정 문자의 기본 방식이 공백 문자로 채우기더라도 0을 쓴다.
^ 결과 문자열의 알파벳 문자를 대문자로 바꾼다.
# 결과 문자열의 대소문자를 뒤바꾼다. (이 플래그는 특정 변환 지정 문자들에만 동작하며 그 중에서도 실제로는 %Z에서만 유용하다.)

십진수로 된 폭 지정자가 (존재하지 않을 수도 있는) 플래그 다음에 선택적으로 올 수 있다. 필드의 원래 크기가 그 폭보다 작으면 결과 문자열 왼쪽을 채워서 지정한 폭으로 맞춘다.

BUGS

출력 문자열이 max 바이트를 넘게 될 경우에 errno를 설정하지 않는다. 이 때문에 그 오류 경우와 format 문자열이 적법하게 길이 0인 출력 문자열을 만들어 내는 경우를 구별할 수 없다. POSIX.1-2001에서는 strftime()에 대해 어떤 errno 설정도 명세하고 있지 않다.

일부 버그 있는 gcc(1) 버전들에서는 %c 사용 시 warning: `%c' yields only last 2 digits of year in some locales라고 나온다. 물론 프로그래머들은 많이 쓰는 날짜 및 시간 표현이 나오는 %c를 쓰는 게 바람직하다. 이런 gcc(1) 문제를 피하기 위한 온갖 희한한 위장 방법들을 만날 수 있는데, 그 중 비교적 깔끔한 방식은 중계 함수를 추가하는 것이다.

size_t
my_strftime(char *s, size_t max, const char *fmt,
            const struct tm *tm)
{
    return strftime(s, max, fmt, tm);
}

요즘은 gcc(1)-Wno-format-y2k 옵션이 있어서 그 경고를 막을 수 있으므로 위 우회 방법이 더는 필요치 않다.

EXAMPLES

RFC 2822 준수 날짜 형식 (%a 및 %b에는 영어 로캘)

"%a, %d %b %Y %T %z"

RFC 822 준수 날짜 형식 (%a 및 %b에는 영어 로캘)

"%a, %d %b %y %T %z"

예시 프로그램

아래 프로그램으로 strftime() 동작을 실험해 볼 수 있다.

다음은 glibc의 strftime() 구현이 내놓는 결과 문자열 예시이다.

$ ./a.out '%m'
Result string is "11"
$ ./a.out '%5m'
Result string is "00011"
$ ./a.out '%_5m'
Result string is "   11"

프로그램 소스

#include <time.h>
#include <stdio.h>
#include <stdlib.h>

int
main(int argc, char *argv[])
{
    char outstr[200];
    time_t t;
    struct tm *tmp;

    t = time(NULL);
    tmp = localtime(&t);
    if (tmp == NULL) {
        perror("localtime");
        exit(EXIT_FAILURE);
    }

    if (strftime(outstr, sizeof(outstr), argv[1], tmp) == 0) {
        fprintf(stderr, "strftime returned 0");
        exit(EXIT_FAILURE);
    }

    printf("Result string is \"%s\"\n", outstr);
    exit(EXIT_SUCCESS);
}

SEE ALSO

date(1), time(2), ctime(3), nl_langinfo(3), setlocale(3), sprintf(3), strptime(3)


2021-03-22