IDL/Programming

Julian Date 및 날짜/시간 포맷(Date Format) 다루기 [1]

이상우_idl 2019. 7. 7. 21:15
728x90
반응형

Julian Date는 율리우스일 또는 J.D.라고도 부르는 날짜수의 개념으로서, 기원전 4713년 1월 1일 정오(세계표준시 기준)를 기점으로 계산되는 값입니다. Julian Date는 일명 Julian Day라고 부르기도 하는데 기본적으로 단위가 일(day)이기 때문입니다. 사실 우리는 날짜를 얘기할 때 항상 년/월/일/시/분/초 등의 단위를 사용하는 것이 너무나 일상적이고 당연합니다. 하지만 날짜를 "데이터"로서 취급해야 하는 프로그래밍의 세계에서는 절대적인 기준을 갖는 하나의 데이터 값으로서 날짜를 취급하기 위하여 이와 같은 Julian Date(이하 JD로 통칭)의 개념을 사용하는 것은 필수적입니다.


IDL에서도 날짜를 JD의 형태로 다루는데 필요한 관련 기능들이 내장되어 있습니다. 그 중에서도 가장 대표적인 기능인 JULDAY 함수에 대하여 먼저 살펴보고자 합니다. 이 함수는 특정한 년/월/일/시/분/초 값에 대응되는 JD 값을 산출하는 역할을 합니다. 예를 들어서 2019년도 6월 21일 17시 30분 00초에 대한 JD 값을 얻고자 할 경우, 이 과정을 IDL 콘솔창에서 실행하려면 다음과 같이 하면 됩니다.


IDL> tj = JULDAY(6, 21, 2019, 17, 30, 0)


이와 같이 JULDAY 함수를 사용할 때 인자값들은 월, 일, 년, 시, 분, 초의 순서가 되어야 한다는 것을 유의해야 합니다. 그리고 인자값들을 위와 같이 월, 일, 년, 시, 분, 초 모두 다 사용할 수도 있지만 그냥 월, 일, 년 또는 월, 일, 년, 시 등과 같이 뒷부분의 몇몇 인자들을 생략할 수도 있습니다. 어쨌든 위의 명령을 실행하여 얻어진 tj가 바로 JD 값에 해당되는데, 이 값에 대한 정보를 출력해보면 다음과 같습니다.


IDL> HELP, tj

TJ              DOUBLE    =        2458656.2


이와 같이 tj는 2배 정밀도(Double Precision) 실수형의 값으로 얻어집니다. JD 값은 기본적으로 일(day) 단위이지만 시/분/초 단위의 값들도 구분할 수 있어야 하기 때문에 일반 실수형(4바이트)이 아닌 2배 정밀도 실수형(8바이트)으로 정의됩니다. JULDAY 함수의 각 인자값은 단일값이 될 수도 있지만 경우에 따라서는 배열이 될 수도 있습니다. 만약 2019년 6월 11일부터 20일까지 매일 17시 30분 00초에 해당되는 총 10개의 JD 값들을 얻고자 한다면, 다음과 같이 일(day) 값들만 배열의 형태로 정의하여 처리할 수도 있습니다.


IDL> days = INDGEN(10)+11

IDL> tjs = JULDAY(6, days, 2019, 17, 30, 0)

IDL> HELP, tjs

TJS             DOUBLE    = Array[10]


위의 예제에서는 일(day)에 대해서만 배열을 적용했지만 월, 일, 년, 시, 분, 초 모든 인자들이 각각 단일값 또는 배열이 될 수 있습니다. 따라서 각 인자가 여러 개의 값들로 구성된 배열일 경우에는 위치에 맞게 배열들을 인자로 투입하여 사용하면 됩니다. 예를 들어 월, 일, 년, 시에 해당되는 100개씩의 값들로 구성된 months, days, years, hours 배열이 있다고 가정한다면, 다음과 같이 처리하여 100개의 JD 값들로 구성된 배열을 얻을 수 있습니다.


IDL> tjs = JULDAY(months, days, years, hours)

IDL> HELP, tjs

TJS             DOUBLE    = Array[100]


이와 같이 년, 월, 일, 시, 분, 초 각각에 대한 데이터를 단일값 또는 배열 중 어떤 형태로든 갖고 있을 경우에는, 그 값들을 그대로 JULDAY 함수에 투입하여 JD 값들을 얻을 수 있습니다. 그런데 그렇지 못한 경우도 있습니다. 즉 날짜/시각이 아닌 실제 데이터 값들로만 구성된 배열은 갖고 있는데, 날짜/시각에 대해서는 언제부터 언제까지 일정한 간격으로 나누어져 있다는 정도의 정보만 갖고 있는 경우입니다. 예를 들면, 48개의 기온 측정값들로 구성된 데이터를 배열의 형태로는 갖고 있는데, 각각의 값에 대응되는 날짜/시각에 대해서는 배열 형태의 데이터를 갖고 있지 못하고 그 대신 2019년 6월 20일 00시부터 6월 21일 23시까지 1시간 간격이라는 정보만 주어진 경우를 가정해 봅시다. 그러면 이러한 날짜/시각 값들에 해당되는 JD 값들로 구성된 배열을 내가 직접 만들어야 합니다. 이런 경우에는 TIMEGEN 함수를 사용하면 됩니다.


TIMEGEN 함수는 원하는 날짜 범위 및 간격 또는 갯수를 직접 설정하여 연속적인 JD 값들로 구성된 배열을 산출하는 역할을 합니다. 그런데 TIMEGEN 함수를 제대로 사용하려면 날짜 구간의 시작 날짜에 대한 JD 값을 갖고 있는 것이 좋습니다(물론 그렇지 않은 경우도 있습니다). 지금과 같은 경우라면 시작 날짜가 2019년 6월 20일 00시이므로 JULDAY 함수를 다음과 같이 활용하면 됩니다.


IDL> tj0 = JULDAY(6, 20, 2019, 0)


이와 같이 먼저 시작 시점의 날짜에 대한 JD 값을 tj0라는 변수로 생성합니다. 그 다음에 TIMEGEN 함수를 다음과 같이 사용합니다.


IDL> tjs = TIMEGEN(48, START=tj0, UNITS='hours')


여기서 맨앞의 48이란 숫자는 생성되어야 할 JD 값들의 갯수입니다. 그리고 UNITS 키워드에 'hours'라는 문자값을 주면 생성될 JD 값들의 간격이 시간(hour)이 됩니다. 이렇게 얻어진 tjs는 48개의 JD 값들로 구성된 배열로서 생성되었음을 다음과 같이 확인할 수 있습니다.


IDL> HELP, tjs

TJS             DOUBLE    = Array[48]


그런데 TIMEGEN 함수를 사용할 때, 끝 시점에 대한 JD 값까지 직접 명시하는 경우도 있습니다. 그럴 경우에는 갯수는 생략합니다. 즉 다음과 같이 끝 시점의 JD 값인 tj1을 추가적으로 사용하는 방법도 있습니다.


IDL> tj1 = JULDAY(6, 21, 2019, 23)

IDL> tjs = TIMEGEN(START=tj0, FINAL=tj1, UNITS='hours')


결과는 어차피 같습니다.


IDL> HELP, tjs

TJS             DOUBLE    = Array[48]


그런데 이렇게 생성된 48개의 JD 값들이 정말로 내가 의도했던 바와 같은 값들인지 궁금합니다. 그렇다면 tjs의 값들을 출력해보면 되겠지요. 하지만 반복형 구문을 적절히 이용하여 tjs 배열의 모든 값들을 직접 출력을 해보면 결과는 다음과 같습니다.


IDL> FOR j = 0, N_ELEMENTS(tjs)-1 DO PRINT, tjs[j]

       2458654.5

       2458654.5

       2458654.6

       2458654.6

       2458654.7

       2458654.7

       2458654.8

       2458654.8

       2458654.8

       2458654.9

       2458654.9

       2458655.0

       2458655.0

       2458655.0

       2458655.1

       2458655.1

       2458655.2

       2458655.2

       2458655.3

       2458655.3

       2458655.3

       2458655.4

       2458655.4

       2458655.5

       2458655.5

       2458655.5

       2458655.6

       2458655.6

       2458655.7

       2458655.7

       2458655.8

       2458655.8

       2458655.8

       2458655.9

       2458655.9

       2458656.0

       2458656.0

       2458656.0

       2458656.1

       2458656.1

       2458656.2

       2458656.2

       2458656.3

       2458656.3

       2458656.3

       2458656.4

       2458656.4

       2458656.5


당연히 이 값들은 내가 의도했던 JD 형태의 값들이 맞습니다. 다만 이렇게 출력되어서는 우리가 바로 날짜를 제대로 알아보는 것은 사실상 불가능합니다. 출력되는 내용이 적어도 우리에게 익숙한 "년월일시분초"와 같은 형태로는 나와줘야 우리가 알아볼 수 있을 것입니다. 따라서 이런 경우에는 JD 형태로 되어 있는 값을 우리가 알아볼 수 있는 형태로 출력하게 해주는 특수한 포맷(FORMAT)을 사용하는 것이 좋습니다. 바로 C() 포맷 코드를 사용하는 방법입니다. 일단 지금과 같은 경우에는 다음과 같이 PRINT 문에서 FORMAT 키워드를 사용하면서 이러한 포맷 코드를 명시해주면 됩니다.


IDL> FOR j = 0, N_ELEMENTS(tjs)-1 DO PRINT, tjs[j], FORMAT='(C(CYI4.4, CMoI2.2, CDI2.2, CHI2.2))'


다만 여기서 사용된 포맷 코드의 문법이 다소 생소할 수 있습니다. 포맷 코드라고 하면 흔히 I, F, A, X 등과 같은 것들이 많이 사용되는데, 지금 사용된 C() 포맷 코드의 경우는 IDL에서 날짜/시각 값을 출력할 때 사용하는 특수한 포맷 코드라고 보시면 됩니다. C() 포맷 코드의 사용법에 대한 자세한 내용은 IDL 도움말에서 다음과 같은 하위 섹션에 소개가 되어 있습니다.


IDL > Language > Miscellaneous Topics > Fortran-Style Format Codes


일단 여기서 사용된 내용 위주로 간단히 설명 드리자면, CYI4.4라고 하면 년도(year)에 해당되는 값을 정수 4자리로 표기하라는 의미이고, CMoI2.2는 월(month)에 해당되는 값을 정수 2자리로 표기하라는 의미입니다. 그리고 그냥 CMoI2가 아니라 CMoI2.2라고 한 이유는 월의 값이 한 자리일 경우에도 십의 자리에는 0을 채워넣으라는 의미입니다. 즉 6월일 경우 그냥 ' 6'이 아니라 '06'으로 표기되도록 하기 위해서입니다. CDI2.2는 일(day)에 해당되는 값을 정수 2자리로 표기하라는 의미이고, CHI2.2는 시각(hour)에 대응됩니다. 그리고 위에서는 사용되지 않았지만 분(minute)의 경우는 CMI, 그리고 초(second)의 경우는 CSI 코드가 사용됩니다. Month와 Minute가 시작 알파벳이 M으로 동일하기 때문에 월은 CMoI, 분은 CMI로 구분되어 있습니다. 어쨌든 위와 같은 명령을 실행하면 출력되는 내용은 다음과 같습니다.


2019062000

2019062001

2019062002

2019062003

2019062004

2019062005

2019062006

2019062007

2019062008

2019062009

2019062010

2019062011

2019062012

2019062013

2019062014

2019062015

2019062016

2019062017

2019062018

2019062019

2019062020

2019062021

2019062022

2019062023

2019062100

2019062101

2019062102

2019062103

2019062104

2019062105

2019062106

2019062107

2019062108

2019062109

2019062110

2019062111

2019062112

2019062113

2019062114

2019062115

2019062116

2019062117

2019062118

2019062119

2019062120

2019062121

2019062122

2019062123


비로소 우리가 쉽게 알아볼 수 있는 날짜/시각의 형태로 출력이 되었습니다. 만약 다음과 같이 분, 초에 해당되는 포맷코드까지 적용하면 출력되는 결과는 다음과 같습니다.


IDL> FOR j = 0, N_ELEMENTS(tjs)-1 DO PRINT, tjs[j], FORMAT='(C(CYI4.4, CMoI2.2, CDI2.2, CHI2.2, CMI2.2, CSI2.2))'

20190620000000

20190620010000

20190620020000

20190620030000

20190620040000

20190620050000

20190620060000

20190620070000

20190620080000

20190620090000

20190620100000

20190620110000

20190620120000

20190620130000

20190620140000

20190620150000

20190620160000

20190620170000

20190620180000

20190620190000

20190620200000

20190620210000

20190620220000

20190620230000

20190621000000

20190621010000

20190621020000

20190621030000

20190621040000

20190621050000

20190621060000

20190621070000

20190621080000

20190621090000

20190621100000

20190621110000

20190621120000

20190621130000

20190621140000

20190621150000

20190621160000

20190621170000

20190621180000

20190621190000

20190621200000

20190621210000

20190621220000

20190621230000


오늘은 JULDAY, TIMEGEN, C() 포맷 코드 등에 관하여 살펴보았습니다. 날짜/시각을 데이터로서 취급하는 일은 실전에서 매우 요긴하게 사용됩니다. 현장에서 얻어지는 데이터들 중 이와 같은 기반의 시계열 데이터들이 굉장히 많기 때문입니다. 물론 그렇기 때문에 처리 방법에있어서도 매우 다양한 경우들이 존재합니다. 오늘은 제가 기본적인 내용만 언급을 했고, 일단 이 글의 제목에 [1]이라는 번호를 달아두었습니다. 이어지는 내용이 준비가 되면 조만간 [2]도 올리도록 하겠습니다.

반응형