반응형
블로그 이미지
개발자로서 현장에서 일하면서 새로 접하는 기술들이나 알게된 정보 등을 정리하기 위한 블로그입니다. 운 좋게 미국에서 큰 회사들의 프로젝트에서 컬설턴트로 일하고 있어서 새로운 기술들을 접할 기회가 많이 있습니다. 미국의 IT 프로젝트에서 사용되는 툴들에 대해 많은 분들과 정보를 공유하고 싶습니다.
솔웅

최근에 올라온 글

최근에 달린 댓글

최근에 받은 트랙백

글 보관함

카테고리

코로나에서 time, date 다루기

2013. 1. 18. 06:49 | Posted by 솔웅


반응형
Posted on . Written by

“Time keeps on slippin’, slippin’, slippin’, into the future.” —Steve Miller Band / Fly Like An Eagle


어떤 프로그래밍 언어로 time 관련해서 작업을 하는 것은 프로그래머에게 혼란을 주게 됩니다. 이번주의 튜토리얼은 time 과 date 에 대해 다룹니다. 그리고 date 계산, time zone 문제 그리고 date formatting 관련해서 예제를 보여드리겠습니다.


코드를 보기전에 먼저 이해해 둬야할 몇가지 중요한 개념들이 있습니다. 컴퓨터에게는 time 의 기본 유닛은 1초 입니다. 이 값으로 우리는 date 도 계산할 수 있고 fraction 들도 다루고 심지어 time 의 더 작은 순간들도 계산해 냅니다. 사람에게 1초는 짧은 시간이지만 컴퓨터에게는 아주 긴 시간입니다. 사람들도 긴 시간의 경우 세분화해서 사용하듯이 컴퓨터도 마찬가지 입니다. 어떤 함수는 1/1000 초(milliseconds ) 단위로 작업을 하고 이것을 uSec 나 uSeconds으로 쓰기도 합니다. 더 좋은 컴퓨터는 이것보다 더 짧은 시간을 다루기도 합니다. 코로나에서는 3가지 종류의 time measurement 를 사용합니다.




Microseconds and Milliseconds


Microseconds 는 아래와 같이 call 하시면 됩니다 :


system.getTimer()


이 call은 여러분의 app 이 시작한 이후의 시간을 return 해 줍니다. 그 값은 milliseconds (1/1000ths) 단위 입니다. 대부분의 device들에서는 아래와 같은 값을 return 할 겁니다.


1839.3949


이 time은 microsecond resolution 으로 측정되는데요 여러분에게 보일 때는 milliseconds 로 변환해서 보여드리는 겁니다. 그리고 소숫점 아래는 microseconds 를 나타내죠. 이 마이크로세컨드 단위가 필요한 경우가 있을지 궁금해 하실수도 있습니다. 아마 실제로는 필요가 없겠죠. 아마 GPS 데이터를 다루거나 Einstein’s Pedometer (앱 이름인데요. 여러분이 빠르게 움직이면 얼마나 젊어지는지를 보여주는 앱입니다.) 작업을 하게 되면 이 microsecond resolution 이 필요할 수도 있습니다. 하지만 우리의 앱에서는 1초당 30~60 프레임을 사용할 거니까 1/30 에서 1/60 초 단위로 작업을 하게 될 겁니다. 그러니까 1/1000 초 보다 더 정교한 시간계산은 이 앱에서는 그다지 중요하지는 않습니다.


milliseconds와 관련해서 사용하는 함수들은 아래와 같습니다.


이 함수들은 millisecond 단위를 accept 합니다. 그러니까 이 함수에 값을 5000을 주면 그 시간은 5초가 되는 겁니다. 1/2 초는 500이 되겠죠. 2와 1/2 초는 2500이 됩니다.


timer.performWithDelay(500, someFunction)  -- a half second delay


초를 원하는 값으로 바꾸시려면 1000을 곱하면 되겠죠.


Dealing With Longer Periods of Time


위에서 언급했듯이 컴퓨터에서 time 의 기본적인 유닛은 1초입니다. iOS와 안드로이드 (Mac OS X 도 마찬가지로) 모두 Unix 에서 파생된 운영체제에 근거해 있습니다. 그리고 그런 운영체제에서 표준 "time" 함수는 1970년 1월 1일 자정 이후부터 경과된 시간을 초단위로 return 해 줍니다.  마이크로소프트는 좀 다른데요, 하지만 이미 많은 앱들이 C언어를 사용해서 만들어졌고 이 언어는 Unix 에 기반해 있거든요. 그리고 그 언어의 라이브러리도 이 time reference pointEpoch를 사용하구요.


마이너스일 경우에는 1970년 이전이 되겠죠. positive times 일 경우에는 1970년 이후가 되겠구요. 코로나에서는 os.time() API call을 사용해서 1970년 1월 1일 이후 지금까지의 number of seconds 를 얻을 수 있습니다.


print( os.time() )  -- outputs something like: 1358015442


사실 저 숫자만 나온다면 알아볼수가 없으니까 별로 의미가 없겠죠. 이것을 date math로 계산해서 실제 생활에서 쓰이는 날짜와 시간으로 바꿔야 알아볼 수가 있을 겁니다. 그런데 게임을 한번 생각해 보세요. turn-based game 이 있는데 유저가 너무 오랫동안 게임을 하고 있어서 어떤 "nudge" 가 필요한 경우 아래와 같이 마지막 move의 시간을 저장할 필요가 있을 겁니다.


player[1].lastMove = os.time()

그리고 나서 지난 한시간 동안 어떤 move 가 있는지 체크하고 싶으면 아래와 같이 하시면 됩니다.


local now = os.time()
if ( now > player[1].lastMove + 3600 ) then
  -- nudge the player
end


3600은 어디서 왔을까요? 60초 곱하기 60분을 하면 그 값입니다. 즉 1시간이죠. 그러니까 아래와 같이 하셔도 됩니다.


local now = os.time()
if ( now > player[1].lastMove + (60 * 60) ) then
  -- nudge the player
end

Lua 의 pre-processor 는 (60*60) 를 3600으로 convert 할겁니다. 그러면 여러분이 따로 미리 계산할 필요는 없겠죠. 아래와 같이 미리 날짜, 시간, 분을 상수로 정의해서 사용하셔도 됩니다.


DAY = 24 * 60 * 60
HOUR = 60 * 60
MINUTE = 60

local now = os.time()
if ( now > player[1].lastMove + HOUR ) then
  -- nudge the player
end


이렇게 초단위로 다룬 값으로 date math 를 사용하는 것은 아주 쉽습니다.


  • Is your time older than a week? Use ( 7 * 24 * 60 * 60 ).
  • Set an alarm in 30 days? Use ( 30 * 24 * 60 * 60 ).


또한 한달에 몇일이 있는지, 윤년인지 아닌지 등등도 계산할 것을 걱정하지 않으셔도 욉니다.

Working With Dates


Dates(날짜)와 관련해서는 조금 신경을 쓰셔야 됩니다. 왜냐하면 날짜를 표시할 때는 strings 를 사용하니까요.


  • April 1, 2010 4:53pm
  • April 1, 2010 4:53 P.M.
  • 4/1/10 16:53 MT
  • Sun Jan 13 15:17:32 EST 2013
  • 13-JAN-13 15:17


개발자로서 날짜를 component values로 parse 할 필요가 있습니다. date/time string의 각 파트별로 get 하셔야 합니다. (month, day, year, hour, minute, seconds and time zone). 만약에 month를 string 으로 가지고 있다면 이 string 을 number 로 그리고 timestamp로 convert 할 lookup table 을 사용하실 수 있습니다.


local monthNumbers = {}
monthNumbers["January"] = 1
monthNumbers["February"] = 2
monthNumbers["March"] = 3
--etc...
monthAsInteger = monthNumbers[monthString]


가장 일반적인 date format 중의 하나는 ISO-8601 time format입니다. 그 string 값은 대충 아래와 같습니다.


2012-01-12T12:04:35.03-0400


이 date 를 parse 하시려면 이 여러 bit들을  패턴을 사용한 각각의 값으로 break down 하기 위해 string:match string function 을 사용할 겁니다.


local pattern = "(%d+)%-(%d+)%-(%d+)%a(%d+)%:(%d+)%:([%d%.]+)([Z%p])(%d%d)%:?(%d%d)"
local year, month, day, hour, minute, seconds, tzoffset, offsethour, offsetmin =
  dateString:match( pattern )

Lua 를 처음 접하시는 분들 에게는 위 코드가 많이 복잡하게 보일 겁니다. 신택스가 그렇게 간단하지는 않죠? 이해하기 쉽도록 break down 해 보죠.


2012-01-12   T   12:04:35.03   -0400


T 이전 부분은 date 입니다. 알아보기 쉽고 또 나누기 쉽죠.  연,월,일이 숫자로 있고 각각 hyphen 으로 나눠져 있습니다. T는 시간 부분이 시작한다는 의미입니다. 그 다음에는 시,분,초를 가리키는 숫자들이 나오고 각각 colon들로 나눠져 있죠. 이 중 초단위는 floating point number 라는 것을 기억해 두세요. 그 다음부분에는 옵션으로 time zone이 나옵니다. 이 timezone 과 관련해서는 조금 더 얘기를 나눌 겁니다.


string:match() function은 어떤 패턴을 보여주는데요. string 의 span 에 대해 “wildcard” type searching 을 사용하는 그런 패턴이죠. 위의 경우에는 %d+ pattern을 상요해서 숫자를 찾고 있습니다. 이렇게 함으로서 Lua 에게 0에서 9까지의 숫자열을 찾도록 만들죠. + 표시는 한개 이상의 숫자열을 찾는 다는 것을 알려 주는 겁니다. 예를 들어 2012 같은 숫자열을요.


이 값들을 알아볼 수 있는 변수에 저장하기 위해 괄호 () 안에 어떤 pattern set들을 넣게 됩니다. 그러면 Lua에게 우리가 retrieve 하기 원하는 date/time 의 각각의 단위들을 알려줄 수가 있습니다. date 부분은 hyphen 들이 있는데요. 이것은 각 컴포넌트를 분리하는 기호입니다.


(%d+)%-(%d+)%-(%d+) -- gets the year, month and day
%a -- handles the single letter T
(%d+)%:(%d+)%:([%d%.]+) -- gets the hour, minutes and seconds



마지막 부분을 보세요. 숫자하고 점(.) 이 있죠? 초가 floating point value 이기 때문에 이렇게 찾는 겁니다. string matching에서 점(.) 은 “magic character” 입니다. 이 점 앞에 % 를 넣으셔야 Lua 는 실제 점(.) 을 찾을 겁니다.


마지막으로 + 나 - 구분자 다음에 timezone 정보가 옵니다. 혹은 Z 나 UTC 를 사용하기도 하죠. string:match function 은 각각의 match 되는 패턴들에 대한 값들을 return 할 겁니다. 그리고 그 값들을 변수에 담게 되죠. ISO-8601 standard는 timezone time 을 +/-HHMM+/-HH:MM를 사용합니다. 만약 이 timezone 정보가 없으면 여러분이 속해 있는 local timezone 을 사용할 겁니다. 아래의 패턴은 좀 복잡해 보이긴 하는데요. timezone 정보를 처리하는 코드 입니다.


([Z%p])(%d%d)%:?(%d%d)


Z 나 punctuation character 인 + 나 - 다음에 %p가 옵니다. 그 다음에는 두자리 숫자가 와야 됩니다. 그 다음에 colon 은 옵션인데요. 두개의 숫자가 더 올 수도 있습니다. 이 패턴은 tzoffset, offsethour, offsetmin등을 return 합니다. 이것을 date math 에서 사용할 숫자로 convert 하기 위해 os.time() function (documentation) 을 사용합니다.


local timestamp = os.time(
  {year=year, month=month, day=day, hour=hour, min=minute, sec=seconds} )



이제 1970년 1월 1일 이후 몇초가 경과했는지에 대한 값을 갖게 됐습니다. 그리고 이것을 date math 로 이용할 수 있게 됐구요. 한가지 문제가 있는데요. 아직까지 timezone 부분에 대해 일을 마치지는 않았습니다. 다행히 그 작업은 어렵지 않은데요. 지금 이미 기본 시간을 seconds 로 갖고 있으니까 계산만 하면 되곘죠. Z는 time 이 Coordinated Universal Time (UTC)라는 것을 가리키는 겁니다. 만약 이것이 UTC 나 local time 이라면 따로 timezone 과 관련해서 작업할 것은 없습니다.


local offset = 0
if ( tzoffset ) then
  if ( tzoffset == "+" or tzoffset == "-" ) then  -- we have a timezone!
    offset = offsethour * 60 + offsetmin
    if ( tzoffset == "-" ) then
      offset = offset * -1
    end
    timestamp = timestamp + offset
  end
end


os.time()은 여러분이 있는 곳의 timezone 을 사용해서 값을 return 할 겁니다.


아래 코드는 여러분이 라이브러리로 사용할 수 있는 샘플 예제 입니다.


function makeTimeStamp(dateString)
  local pattern = "(%d+)%-(%d+)%-(%d+)%a(%d+)%:(%d+)%:([%d%.]+)([Z%p])(%d%d)%:?(%d%d)"
  local year, month, day, hour, minute, seconds, tzoffset, offsethour, offsetmin =
    dateString:match(pattern)
  local timestamp = os.time(
    {year=year, month=month, day=day, hour=hour, min=minute, sec=seconds} )
  local offset = 0
  if ( tzoffset ) then
    if ( tzoffset == "+" or tzoffset == "-" ) then  -- we have a timezone!
      offset = offsethour * 60 + offsetmin
      if ( tzoffset == "-" ) then
        offset = offset * -1
      end
      timestamp = timestamp + offset
    end
  end
  return timestamp
end


Final note:  여기서는 아직 Daylight Savings Time(써머타임)을 적용하지는 않은 상황입니다. Daylight Savings Time 은 이 튜토리얼에서 다루지 않습니다.


Converting a Timestamp to Something Readable


어떤 경우에는 이 time 을 유저가 알아볼 수 있는 date string으로 표현할 필요가 있을 겁니다. 이게 바로 os.date() function의 목적이죠. (documentation).


os.date() function 은 두개의 파라미터를 받습니다. 둘 다 옵션인데요. 이 옵션 없이 call 하면 현재 있는 지역의 timezone 에 맞는 date/time 을 return 할 겁니다. 아마 아래와 같은 형식일 겁니다.


Sat Jan 12 14:07:30 2013


아주 다양한 형식으로 표현할수 있는데요. 다양한 formatting parameter 들을 이용하시면 됩니다. 이 format parameter들은 Unix/C++ library function strftime에 근거합니다.


print( os.date("%A") )  -- prints out a string representing the weekday.
print( os.date("%A %l:%M%p") )  -- prints out "Saturday 2:30PM"

디폴트로 os.date()가 return 하는 값은 여러분의 지역이나 time zone 에 근거할 겁니다.  RSS feeds, Corona Cloud date 등에서 일반적으로 사용하는 위에서 사용한 날짜 형식을 생성하기 원하신다면 UTC 에 근거한 output 을 받아야 합니다. 이것은 format parameter 앞에 ! 를 붙이시면 됩니다.


print( os.date("%FT%X%z") )  -- outputs: 2013-01-12T15:30:09-0500

or if you wanted it in UTC:

print( os.date("!%FT%XZ") )  -- outputs: 2013-01-12T19:30:09Z



strftime에 있는 다양한 formatting parameter 들은 다양한 format 으로 month, day, year, weekday, am/pm를 얻을 수 있도록 합니다. 



Counting Down Time


흔히 하는 질문으로 "어떻게 countdown timer 를 만들수 있죠?" 가 있습니다.

가장 좋은 방법은 직접 한번 구현해 보는 걸겁니다. 만약에 60안에 한 레벨을 끝내기를 원한다면 60초 후에 뭔가 이벤트를 발생시키기 위해 timer.performWithDelay()를 사용하시면 됩니다. 보기 좋은 UI output 과 함께 좀 더 그럴듯한 방법을 원하신다면 enterFrame event 에 Runtime listener를 사용하실 수도 있습니다. 그렇게 하면 시간을 체크할 수 있습니다. 혹은 1초 timer 를 계속 반복할 수도 있겠죠. 그럼 필요할 때 쉽게 pause 시키고 또 resume 시킬 수 있을 겁니다.


-------- RUNTIME METHOD --------
local startTime = os.time()
local levelTime = 60
local displayTime = display.newText(levelTime, 0, 0, "Helvetica", 20)

local function checkTime(event)
  local now = os.time()
  displayTime.text = levelTime - (now - startTime)
  if ( now > startTime + levelTime ) then
    -- code to end the level
  end
end
Runtime:addEventListener("enterFrame", checkTime)
-------- REPEATING TIMER METHOD --------
local levelTime = 60
local displayTime = display.newText( levelTime, 0, 0, "Helvetica", 20 )

local function checkTime(event)
  levelTime = levelTime - 1
  displayTime.text = levelTime
  if ( levelTime <= 0 ) then
    -- code to end the level
  end
end
timer.performWithDelay( 1000, checkTime, levelTime )



In Summary


Corona에서 time 과 date를 다루는 것은 약간 복잡한 신택스를 다뤄야 합니다. 이 튜토리얼이 그것을 이해하는데 도움이 되셨으면 합니다. 위의 예제들을 활용해서 앱 개발시 이용하시면 훨씬 수월하실 겁니다.


반응형