Intro

App 을 만들다보면 날짜를 다뤄야하는 상황을 종종 마주치게 되는데요. 그동안은 원하는 정보를 적당히 구글에서 찾아 문제를 해결하고 따로 공부하는 시간을 가져본 적이 없었습니다. 날짜는 여러가지 앱에서 사용되는 빈도가 높은만큼 제대로 공부하고 지나가면 앞으로 도움이 많이 될 것 같아 공부해 봅니다.


Prerequisite

Cocoa Touch Framework 에는 날짜를 구현할 수 있는 다양한 방법이 존재하는데요. Swift 언어 이전에는 class 타입으로 구성되어 있는 구현방식을 제공했습니다. 아마 개발을 시작한지 어느정도 되었다면 자주 보았을 만한 NS 접두어로 시작하는 것들이 바로 그것이에요 ㅎㅎ

하지만 Swift 라는 언어가 생겨난 이후부터는 struct 타입을 사용해 구현된 방식이 제공되었고, 현재는 대부분 이 struct 방식을 사용한 날짜처리로 코드가 구현됩니다. 왜 struct 를 사용하게 된건지는 저도 아직 잘 모릅니다

아무튼!! class 를 사용하는 방식은 Objective-C 에서 주로 사용되었고 지금은 거의 사용되지 않는만큼, 이번 포스팅에서는 struct 로 구현된 방식에 대해서만 공부하도록 하겠습니다.

사실 두 가지가 사용법이 어느정도 비슷하기 때문에 하나만 제대로 공부하면 나머지도 어떻게 사용하면 될 지 감이 온다고 하네요. 진짜로!??

  • class: NSdate, NSCalendar, NSDateComponents, NSTimeZone, NSLocale
  • struct: Date, Calendar, DateComponents, TimeZone, Locale

참고로 위에 나열된 Date, Calendar 등의 타입은 모두 브릿징을 지원하기 때문에 필요한 상황에 as 연산자로 간단하게 타입을 캐스팅해서 사용이 가능합니다.


Practice

UIKit 에서 날짜를 구현할 수 있는 방법과 그 종류 대해 알아보았으니 이제 직접 코드를 작성해 보면서 CalendarDate 를 인스턴스화 하고, 원하는 값을 지정할 수 있도록 연습해 보겠습니다. 백날 이론만 읽어봐야 실제로 직접 한번 써보는 것만 못하더라고요.


Calendar 생성하기

캘린더를 인스턴스화할 수 있는 방법은 3가지가 있습니다. 이 중 2번째와 3번째 방법이 비슷하게 생각될 수 있고 실제로도 어느정도 비슷한데요. 차이점을 적어놨으니 잘 살펴보시고 그래도 이해가 잘 되지않는다면 설정에서 캘린더 종류를 바꿔가며 차이점을 확인해보세요.

공식문서의 Calendar

캘린더를 생성할 수 있는 방법 3가지는 이렇습니다.

  • identifier 를 지정하여 특정 종류의 캘린더를 사용하는 방법
  • 사용자가 아이폰에 설정해 놓은 캘린더의 종류를 시스템 설정으로부터 최초에 한번 전달받아 사용하는 방법
  • 사용자가 아이폰에 설정해 놓은 캘린더의 종류를 시스템 설정으로부터 전달받아 사용하며, 이후 설정에서 사용자가 캘린더의 종류를 변경하면 함께 적용할 수 있는 캘린더를 사용하는 방법

정말 2번째와 3번째 방법이 비슷해보이죠? 아래 코드처럼 캘린더를 인스턴스화하고 print 를 찍어가며 설정을 바꿔보면 차이점이 좀 더 명확해집니다.

.current 를 사용한 캘린더는 설정에서 캘린더 종류를 바꿔도 적용되지 않고 .autoupdatingCurrent 는 적용이 됩니다.

let customCalendar = Calendar(identifier: .gregorian) // 직접 캘린더의 종류를 설정, enum 형태로 만들어져 있어 gregorian 외에도 다양한 종류의 캘린더를 확인할 수 있음
let systemCalendar = Calendar.current // 사용자의 아이폰 설정 캘린더 정보를 가져와서 사용하지만, 날짜가 로드된 이후 사용자가 설정에서 변경하는 내용은 적용되지 않음
let autoUpdatingSystemCalendar = Calendar.autoupdatingCurrent // 아이폰에서 설정되어 있는 달력을 기준으로 값을 리턴

이렇게 3가지 방법을 사용해 달력을 생성할 수 있지만, 특별한 이유가 없다면 .current.autoupdatingCurrent 를 통해 사용자가 아이폰에서 설정해 놓은 달력을 기준으로 사용하는 것이 좋습니다.


Date 생성하기

이제 Date 를 구현하는 방법을 알아보겠습니다.

공식문서의 Date

시간은 각 나라마다 다를 수 있는데 만약 별도로 TimeZone 을 명시하지 않는다면 사용자의 아이폰 기본설정을 따라가게 됩니다. 그럼 현재 날짜를 값으로 갖고 있는 변수를 만들어 볼까요?

let now = Date()

정말 간단하네요. 이제 nowprint 해보면 현재 날짜가 출력되는 것을 확인할 수 있을거에요. Date 를 인스턴스화 하는 것만으로도 현재 날짜에 접근할 수 있게됩니다.


TimeInterval 알아보기

특정 시간만큼 지난 시각을 Date 로 만들고 싶다면 Date(timeIntervalSinceReferenceDate:) method 를 사용해 이동하고 싶은 시간을 초로 설정할 수 있습니다. 하지만 이 method 를 사용하기 전에 반드시 알고 지나가야할 개념이 하나 있는데요. 바로 TimeInterval 입니다.

공식문서의 TimeInterval

TimeIntervalSwift 의 기본 타입 중 하나인 DoubleType Alias 된 형태로 단독적으로 사용하는 경우는 거의 없는 것 같고… 주로 Date 와 연계하여 사용됩니다.

let oneSec = TimeInterval(1) // 1초
let oneMin = TimeInterval(60) // 1분
let oneHour = TimeInterval(oneMin * 60) // 1시간
let oneDay = TimeInterval(oneHour * 60) // 1일

TimeInterval 을 사용해 현재의 1분 후를 Date 로 생성해 볼게요.

let oneMinuteAfter = Date(timeIntervalSinceReferenceDate: 60)

쉽네요 ㅎㅎㅎ 1시간은 60분이니까 3600초를 값으로 넣어주면 됩니다.

let oneHourAfter = Date(timeIntervalSinceReferenceDate: 60 * 60)

여태까지는 시간을 미래로만 이동했으니 이제 과거로 돌아가 볼게요. 1시간 전으로 날짜를 세팅하려면 TimeInterval 값을 음수로 바꿔주면 됩니다.

let oneHourBefore = Date(timeIntervalSinceReferenceDate: -60 * 60)

지금 예시에서는 하드코딩 형태로 직접 값을 입력했지만 TimeInterval 형태로 변수를 생성하고 해당 변수를 값으로 넣어줄 수도 있습니다.


DateComponents 사용하기

지금까지 현재 날짜를 기준으로 특정 시간만큼 이동하는 방법을 알아보았고, 이제 직접 원하는 날짜를 지정하여 값을 캘린더에 넣어주는 방법을 공부해보겠습니다. 캘린더에 값을 넣어주기 위해서는 DateComponents 를 이용해야 합니다.

공식문서의 DateComponents

DateComponents 안에는 연월일 등 시간에 관련된 여러가지 속성이 들어있으며 그 값을 우리가 지정할 수가 있습니다. 그럼 DateComponents 를 인스턴스화 시켜 customDateComponents 라는 변수를 생성하고 1988년 8월 5일을 날짜로 세팅한 뒤 캘린더에 넣도록 하겠습니다.

var customDateComponents = DateComponents()
customDateComponents.year = 1988
customDateComponents.month = 8
customDateComponents.day = 5
let customDateComponentsDate = autoUpdatingSystemCalendar.date(from: customDateComponents)

customDateComponentsDate 를 print 해보면 1988년 8월 5일이 출력되는 것을 확인할 수 있습니다.

이제 캘린더에서 필요한 값만 가져오는 방법을 알아볼게요. dateComponents method 를 사용해 여러가지 값들을 한번에 가져오거나, component method 를 사용해 값 하나만을 가져올 수도 있습니다.

let components = autoUpdatingSystemCalendar.dateComponents([.year, .month, .day, .hour], from: now)
let year = autoUpdatingSystemCalendar.component(.year, from: now)
let month = autoUpdatingSystemCalendar.component(.month, from: now)
let day = autoUpdatingSystemCalendar.component(.day, from: now)

마지막으로 두 날짜 사이의 간격을 일 수로 가져오는 방법을 알아보겠습니다.

조금 전 만들어두었던 customDateComponentsDate 와 현재 날짜 사이의 간격을 알아볼게요.

let days = autoUpdatingSystemCalendar.dateComponents([.day], from: customDateComponentsDate!, to: now).day

.day 를 사용하지 않고 print 를 해보면 dayisLeapMonth 두가지가 출력되는 것을 볼 수가 있고, 우리는 그 중 day 값에 접근해 일 수 만 가져오게 할 수 있습니다.


Wrap Up

이렇게 CalendarDate 에 관한 기본적인 내용을 공부해 보았는데요. 처음 접하는 내용이다보니 생소한 부분은 있었지만 그렇게 어려운 내용은 아니었던 것 같습니다. 이제 어느정도 기초를 쌓았으니 나중에 더 다양한 사용법을 배울 때도 좀 더 수월하게 이해할 수 있을 것 같네요 ㅎㅎ