SimpleIsBest.NET

유경상의 닷넷 블로그

Week Of Year 알아내기

by 블로그쥔장 | 작성일자: 2005-10-27 오후 10:47:00
이 글은 오래된 전에 작성된 글입니다. 따라서 최신 버전의 기술에 알맞지 않거나 오류를 유발할 수 있습니다. 저자는 이 글에 대한 질문을 받지 않을 것입니다. 하지만 이 글이 리뉴얼 되면 이 글에 대한 질문을 하거나 토론을 할 수도 있습니다.
모 커뮤니티에서 주어진 날짜가 1년 중 몇 번째 날짜인가를 알아내는 방법에 대해 질문하셔서 글을 써 봅니다. 해당 커뮤니티에 직접 답을 달지 않고 여기에 글을 쓰는 이유는 이전 제 글을 읽어 보시길... 아시는 분은 아시겠지만 ^^;

Implementing Week Of Year

Week Of Year 라... 뭔말 인고 하면... 주어진 날짜가 1년 중 몇 번째 주인가를 알아내는 것을 말한다. 예를 들어 2005년 10월 26일은 몇 번째 주인가? 라는 질문에 대답해 주는 그런 기능이 되겠다. 닷넷 프레임워크로 밥 좀 먹구 산다는 사람이라면 잽싸게 MSDN을 열고 DateTime 클래스를 뒤져 볼 것이다. 그리고 그 클래스에 WeekOfYear 속성이나 GetWeekOfYear 메쏘드가 있는지 살펴보았을 것이다. 뭐 필자도 그랬다...

하지만 DateTime 클래스에는 그딴 속성이나 메쏘드가 없다. 쓰봉... 뭐 간단한 거 같은데 이런 걸 구현 안 해 놨나? 칫... 구현 안 되어 있으면 내가 구현하지 머...

Week of Year Rule

간단히 생각하면 오늘이 1년 중 몇 번째 날짜인가 알아내서 (요건 DateTime 클래스의 DayOfYear 속성을 쓰문 된다), 이 값을 7로 나눈 몫으로 생각하면 될 것 같았다. 그런데... 그게 그렇게 녹녹하지 않더라는 것이다. 예를 들어 올해 처럼 1월 1일이 토요일이라면 이 1월 1일을 2005년의 첫 번째 주(week)로 쳐줄 것인가 말 것인가를 결정해야 한다. 또 한가지는 1주의 시작이 일요일이냐 월요일이냐에 따라서 주어진 날짜가 몇 번째 주인가도 달라진다. 점점 머리가 복잡해 진다. 그런데 더욱 짜증이 나는 건 국가마다 한 주의 시작 요일은 다르다는 점이다(우리나라의 경우 일요일이 시작요일이지만 월요일이 시작요일인 나라도 있다). 우씨... 이거 장난이 아니네... 된장...

Using CultureInfo class

뭐 국가마다 WeekOfYear를 계산하는 방식이 달라지는 정도 되면, 다국어를 아주 잘 지원하고 있는 닷넷 프레임워크에 관련된 기능이 없을 수 없다. 국가마다 그 계산 규칙이 달라지므로 DateTime 같은 클래스에 WeekOfYear 값을 알아내는 속성이나 메쏘드가 있을 리 없다. 그렇다. 국가마다 달라지는 부분은 모두 CultureInfo 클래스 내부에 존재한다. 여기에는 다양한 날짜, 통화, 숫자 표기법 등에 대한 정보가 포함되어 있다. 물론 WeekOfYear를 계산하는 규칙 역시 포함되어 있다.

CultureInfo 클래스의 DateTimeFormat 속성은 국가마다 달라지는 날짜/시간에 대한 정보를 포함하는 DateTimeFormatInfo 클래스를 제공한다. DateTimeInfoFormat 클래스는 해당 국가가 일주일의 시작 요일을 무엇으로 하는지를 정의하는 FirstDayOfWeek 속성과 1년의 첫 번째 주를 결정하는 규칙 역시 CalendarWeekRule 속성에 포함되어 있다. FirstDayOfWeek는 매우 직관적이므로 더 이상 설명이 필요 없을 듯하고 CalendarWeekRule 속성은 추가 설명이 필요할 것 같다. 이 속성은 세가지 값을 반환하는데 그 값들과 각각의 의미는 다음과 같다.

  • FirstDay : 1월1일이 포함된 주를 무조건 첫째 주로 삼는다. (우리나라, 미국 등의 기준)
  • FirstForDayWeek : 1월1일이 포함된 주가 4일 이상인 경우에만 그 해의 첫 번째 주로 삼는다.
    예) 한 주의 시작 요일이 일요일이고 1월1일이 일/월/화/수 중 하나이면 1월1일이 포함된 주는 해당 해의 첫 번째 주이다.
    예) 한 주의 시작 요일이 일요일이고 1월1일이 목/금/토 중 하나이면 1월1일이 포함된 주는 해당 해의 첫 번째 주로 간주하지 않는다.
    예) 2005년 1월 1일은 토요일이므로 1월1일이 포함된 주는 2005년의 첫 번째 주로 간주하지 않는다.
  • FirstFullWeek : 1월의 첫 번째 주가 7일이 아니면 해당 해의 첫 번째 주로 삼지 않는다.
    예) 한 주의 시작 요일이 일요일인 경우, 1월1일이 일요일이 아니라면 1월1일이 포함된 주는 해당 해의 첫 번째 주로 간주하지 않는다.

쓰봉, 졸라 복잡해 보이지만 그럴 듯하다. 닷넷이 제공하는 CultureInfo에 의하면 우리나라의 경우, 한 주의 시작 요일은 일요일이며, 1월 1일이 포함된 주는 무조건 해당 해의 첫 번째 주로 간주한다(FirstDay). 상세한 내용은 MSDN을 참고하기 바란다. 비록 영문이지만 매우 상세히 설명하고 있으니깐 말이다.

이제 규칙을 알아낼 수 있으니, 계산만 하면 된다. 1년의 첫째 주를 결정하는 세가지 경우에 대해 각각 계산을 수행하면 되겠지만 다행이도 이 구현 역시 닷넷 프레임워크에 이미 구현되어 있다. CultureInfo 클래스는 Calendar 속성을 제공하는데, 이 속성은 각 국가 마다 다른 달력 표시, 계산등을 제공해 준다. Calendar 속성은 Calendar 클래스 타입인데(닷넷에서 속성의 이름으로 클래스 이름을 쓰는 경우가 많다), Calendar 클래스의 GetWeekOfYear 메쏘드가 우리가 원하는 WeekOfYear 값을 제공해 준다.

GetWeekOfYear 메쏘드를 호출할 때, CalendarWeekRule 값과 FirstDayOfWeek 값을 매개변수로 넘겨주어야 한다., 이 값은 CultureInfo.DateTimeFormatInfo 속성을 통해 알아낼 수 있음은 이미 언급한 대로이다. CultureInfo를 통해 이들 값을 알아낼 수 있음에도 불구하고 왜 굳이 이들을 매개변수로 취했는지 궁금할 따름이다.

Writing Example Code

이제 필요한 모든 정보를 얻었으니 예제 코드를 작성해 보자. 어떻게 하는가를 알아내는 것이 빡셌을 뿐이지 실제 코드는 졸라 간단하다. 쯔읍...

// 주어진 날짜가 1년 중 몇 번째 주(week)인가를 반환한다.
// CultureInfo.CurrentCulture를 사용하여 달력 규칙을 정한다.
public static int GetWeekOfYear(DateTime targetDate)
{
    
return GetWeekOfYear(targetDate, null);
}

// 주어진 날짜가 1년 중 몇 번째 주(week)인가를 반환한다.
// 달력 규칙은 매개변수로 주어진 CultureInfo를 사용한다.
public static int GetWeekOfYear(DateTime targetDate, CultureInfo culture)
{
    
if (culture == null) {
        culture 
CultureInfo.CurrentCulture;
    
}
    CalendarWeekRule weekRule 
culture.DateTimeFormat.CalendarWeekRule;
    
DayOfWeek firstDayOfWeek culture.DateTimeFormat.FirstDayOfWeek;
    return 
culture.Calendar.GetWeekOfYear(targetDate, weekRule, firstDayOfWeek);
}

리스트1. WeekOfYear 구현

리스트1 코드는 설명하기도 좀 난감할 만큼이나 간단하다. MSDN의 도움을 약간만 받으면 금방 이해하리라 생각한다. 위 코드를 호출하는 코드 역시 대단히 간단하다. 곧 바로 GetWeekOfYear 메쏘드를 호출하면 설치된 운영체제의 국가/언어에 의해 WeekOfYear를 계산할 것이고 명시적으로 CultureInfo 객체를 만들어 호출할 수도 있다.

VB 6.0 & VB.NET

VB 6.0에는 WeekOfYear를 알아내는 간단한 방법이 있다. VB 6.0 런타임이 제공하는 DatePart 라는 함수가 그것이다. 물론 VB.NET 역시 DatePart 메쏘드를 제공한다. Microsoft.VisualBasic 네임스페이스의 DateAndTime 클래스의 스태틱 메쏘드로서 DatePart 메쏘드가 제공되므로 이것을 호출하면 곧바로 WeekOfYear를 구할 수 있다. 상세한 내용은 DatePart 메쏘드에 대한 도움말을 참고하기 바란다.

VB.NET의 런타임일지라도 C#에서 호출이 가능하다. 어차피 같은 닷넷 어셈블리이므로 다를 것이 없지 않은가? 프로젝트에서 Microsoft.VisualBasic.dll 어셈블리를 참조하고 DateAndTime 클래스를 쓰면 된다. 다음과 같이... 리스트1과 같이 구현을 하든지 VB.NET의 런타임 어셈블리를 사용하든지는 개인 취향이겠지만 필자라면 C#으로 작성하는데 VB.NET의 런타임은 좀 이상하지 않은가? (아님 말고... -_-;; )

int weekOfYear DateAndTime.DatePart(DateInterval.WeekOfYear, targetDate,
            FirstDayOfWeek.System, FirstWeekOfYear.System)
;

WeekOfMonth ?

그렇담 주어진 날짜가 해당 월에서 몇 번째 주인가 알아내는 방법은 있는가? 대단히 아쉽게도 이에 관련된 어떤 메쏘드도 관련 규칙을 반환하는 클래스도 없다. 하지만 한 주의 첫째 요일을 알면 그다지 복잡하지 않은 연산으로 GetWeekOfMonth 메쏘드 구현이 가능하다. 개략적인 알고리즘은 해당 월의 1일의 요일과 포함된 주의 마지막 요일(토요일)의 차이값을 날짜에서 빼고, 그 결과를 7로 나눈 몫에 1을 더하면 될 것이다 (직접 코딩은 안 해봐서 모르겠다. 맞나? -_-). 대략 귀차니즘에 예제 코드는 생략~~~ 누구 맘대로? 제니퍼 맘대로~~~ -_-;;

Consideration

예제를 만들면서 한가지 고민이 생겼는데, 올해 2005년처럼 1월 1일이 토요일이면 좀 애매한 상황이 발생하기 때문 이였다. 2005년 1월 달력을 실제 눈으로 보면 필자의 고민이 이해가 갈 것이다.

  2005년 1월 1일은 보는 바와 같이 토요일이다. 닷넷이 제공하는 기본 정보에 의해 코드를 작성하면 2005년 1월 12일은 3번째 주이다. 하지만 얼핏 1월 달력을 보면 1월 12일이 셋째 주 이란 느낌이 전혀 들지 않는다는 것이다. 게다가 "매월 두째 주 화요일"에 어떤 작업을 한다고 가정하면 2005년 1월의 두째 주 화요일은 4일이 맞을 것 같은가? 아니면 11일이 더 맞을 것 같은가? -_-;;

닷넷이 제공하는 규칙으로만 따지자면 1월의 두째 주 화요일은 4일이 맞다. 하지만 웬지 11일 더 맞게 보이는 이유는 무엇일까? 이것에 대한 정답은 없다고 본다. -_-; 해당 프로그램이 1월1일이 토요일인 경우에 해당 주를 쳐줄 것인지 말 것인지를 결정하고 그것을 따르면 될 것이다. 만약 쳐준다면 리스트1의 코드를 수정할 필요는 없다. 만약 그렇지 않다면 리스트1의 코드에서 해당 년도의 1월1일이 토요일인가를 확인하고 그렇다면 Calendar.GetWeekOfYear 메쏘드가 반환한 값에서 1을 빼주는 추가 코드를 넣어야 할 것이다. 구체적인 코드는 생략하도록 하겠다.

간단하리라고 생각하고 "잽싸게 글하나 써야지" 하고 시작했던 포스트가 또 길어져 버렸다. 이번 포스트에는 웬또 링크가 그렇게 많은지... 쓰봉... 세상엔 쉬운 게 읍나 부다... 되~엔~장~



Comments (read-only)
#달의 몇째주인가? 찾다가 여기 소스 참조해서 해결했어요.. 감사합니다. / 초보개발자 / 2006-08-17 오전 9:54:00
GetWeekOfYear 참고 해서 달의 몇째주인가 해결했어요..

감사합니다.

아래는 귀차니즘의 해소.. ^^;

//현재일이 포함된 년중 주와 현재달의 첫째날의 년중 주를 구하여 차로서 해당일의 월간 주간을 알아낼수 있다.
public static int GetCurrentWeekOfMonth(CultureInfo culture)
{
DateTime now = DateTime.Now;
DateTime firstDayOfMonth = System.DateTime.Parse(now.ToString("yyyy-MM-01"));
int firstWeekOfMonth = GetWeekOfYear(firstDayOfMonth, culture);
int nowWeekOfMonth = GetWeekOfYear(now, culture);

return (nowWeekOfMonth - firstWeekOfMonth) + 1;
}