RAP

DATS·TIMS vs Timestamp 잘못 쓰면 큰일 #shorts #SAP #RAP

▶ YouTube에서 보기

📖 개요와 이 글의 목표

RAP(ABAP RESTful Application Programming Model)로 앱을 만들 때 가장 조용히, 그러나 가장 크게 사고가 나는 지점이 날짜/시간 필드입니다. 한국 서버에서 잘 돌던 앱이 독일 사용자에게는 "어제 생성된 주문"으로 보이는 문제가 대표적입니다. 이 글은 수주(Sales Order)의 납기일생성일시 시나리오로 DATS/TIMS와 Timestamp(utclong)의 차이를 비교합니다.

  • DATS/TIMS가 왜 "타임존 없는 문자열"에 가까운지 이해한다
  • abap.utclong(Timestamp)의 UTC 기반 동작 원리를 이해한다
  • RAP CDS·Behavior Definition에서 필드 선언과 검증 로직을 작성한다
  • "업무 날짜 vs 시점(Point in Time)" 의사결정 기준을 세운다

📚 미리 알아두면 좋은 배경

ADT(ABAP Development Tools)에서 CDS View Entity와 Behavior Definition을 한 번이라도 만들어 본 경험이 있으면 충분합니다. Managed RAP BO의 기본 구조(테이블 → CDS → BDEF → 구현 클래스)를 알고 있다면 코드가 훨씬 빠르게 읽힙니다. 타임존 개념(UTC, 오프셋)은 본문에서 다시 설명합니다.

🔧 환경 및 준비 사항

이 예제는 다음 환경을 기준으로 합니다.

  • SAP BTP, ABAP Environment(Steampunk) 또는 S/4HANA 2022 이상(ABAP 7.56+) — abap.utclong은 ABAP 7.54부터 지원되므로 온프레미스 구버전에서는 timestampl로 대체가 일반적입니다
  • Eclipse + ADT 최신 버전
  • 개발 패키지(예: ZSO_DEMO)와 전송 요청

클라우드 환경에서는 sy-datum 직접 사용이 제한되고 cl_abap_context_info를 사용해야 한다는 점도 미리 기억해 두세요.

💡 핵심 개념 — DATS/TIMS vs Timestamp

비유하자면 DATS/TIMS는 벽시계 사진이고 Timestamp는 GPS 좌표입니다. 벽시계 사진("2026-07-22 09:00")은 어느 도시의 벽시계인지 정보가 없습니다. 반면 GPS 좌표(UTC 기준 시점)는 지구 어디서 봐도 동일한 한 점을 가리키고, 보는 사람의 위치(타임존)에 맞춰 다르게 "표시"만 하면 됩니다.

  • DATS: 내부적으로 8자리 문자 YYYYMMDD. 타임존 정보 없음. R/2 시절부터 내려온 레거시 타입
  • TIMS: 6자리 문자 HHMMSS. 역시 타임존 없음. DATS와 분리 저장되어 자정 경계에서 두 필드가 어긋날 수 있음
  • abap.utclong: UTC 기준 100나노초 정밀도의 진짜 시점 타입. 비교·정렬·차이 계산이 안전하고, OData로 노출될 때 Edm.DateTimeOffset으로 매핑되어 클라이언트(Fiori)가 사용자 타임존으로 자동 표시
  • timestampl(DEC 21,7): utclong 이전 세대의 UTC 타임스탬프. 숫자형이라 산술 실수 위험이 있으나 여전히 널리 사용

글로벌 시스템에서 DATS/TIMS로 "생성일시"를 저장하면, 서울 서버가 저장한 20260722 / 090000을 프랑크푸르트 사용자도 그대로 09:00으로 보게 됩니다. 실제로는 그의 새벽 2시에 생성된 주문인데도요. 반대로 납기일 같은 "업무 날짜"는 계약상 합의된 달력 날짜이므로 타임존 변환이 오히려 데이터를 왜곡합니다. 즉 핵심 기준은 이것입니다.

시점(언제 일어났나)은 utclong, 업무 날짜(달력의 어느 날인가)는 DATS. TIMS 단독 사용은 새 개발에서 일반적으로 피하는 것이 권장됩니다.

💻 실전 코드 3단계

1단계 — 기본: 테이블과 CDS View Entity 선언 비교

수주 테이블에서 납기일은 DATS, 생성/변경일시는 utclong으로 선언합니다.

@EndUserText.label : '수주 헤더'
define table zso_order {
  key client        : abap.clnt not null;
  key order_uuid    : sysuuid_x16 not null;
  order_no          : abap.char(10);
  buyer_id          : abap.char(10);
  delivery_date     : abap.dats;      " 업무 날짜: 납기일
  created_at        : abap.utclong;   " 시점: 생성일시(UTC)
  last_changed_at   : abap.utclong;   " 시점: 변경일시(UTC)
}

CDS View Entity에서는 시맨틱 어노테이션으로 RAP 프레임워크에 관리 필드를 알려줍니다.

define root view entity ZR_SalesOrder
  as select from zso_order
{
  key order_uuid            as OrderUuid,
      order_no              as OrderNo,
      buyer_id              as BuyerId,
      delivery_date         as DeliveryDate,
      @Semantics.systemDateTime.createdAt: true
      created_at            as CreatedAt,
      @Semantics.systemDateTime.lastChangedAt: true
      last_changed_at       as LastChangedAt
}

이렇게 선언하면 Managed RAP이 생성/변경 시각을 UTC로 자동 기록합니다. 어노테이션 대상 필드는 utclong 또는 timestampl이어야 하며 DATS+TIMS 조합은 지원되지 않습니다.

2단계 — 실무: 타임존 변환과 납기일 검증(에러 처리)

Behavior Definition에 검증을 등록합니다.

managed implementation in class zbp_r_salesorder unique;
strict ( 2 );

define behavior for ZR_SalesOrder alias SalesOrder
persistent table zso_order
etag master LastChangedAt
lock master
{
  create; update; delete;
  field ( readonly ) OrderUuid, CreatedAt, LastChangedAt;
  validation validateDeliveryDate on save { create; field DeliveryDate; }
}

검증 구현에서는 "오늘"을 구할 때 UTC 시점을 사용자 타임존 날짜로 변환하는 것이 핵심입니다.

METHOD validateDeliveryDate.
  READ ENTITIES OF zr_salesorder IN LOCAL MODE
    ENTITY SalesOrder
    FIELDS ( DeliveryDate ) WITH CORRESPONDING #( keys )
    RESULT DATA(lt_orders).

  " UTC 시점 → 사용자 타임존의 '오늘' 날짜
  DATA(lv_tz) = cl_abap_context_info=>get_user_time_zone( ).
  CONVERT UTCLONG utclong_current( )
    INTO DATE DATA(lv_today) TIME DATA(lv_time)
    TIME ZONE lv_tz.

  LOOP AT lt_orders INTO DATA(ls_order).
    IF ls_order-DeliveryDate IS INITIAL
       OR ls_order-DeliveryDate < lv_today.
      APPEND VALUE #( %tky = ls_order-%tky ) TO failed-salesorder.
      APPEND VALUE #(
        %tky = ls_order-%tky
        %element-deliverydate = if_abap_behv=>mk-on
        %msg = new_message_with_text(
          severity = if_abap_behv_message=>severity-error
          text     = '납기일은 오늘 이후여야 합니다' )
      ) TO reported-salesorder.
    ENDIF.
  ENDLOOP.
ENDMETHOD.

failed/reported에 필드 마커까지 채우면 Fiori 화면에서 해당 필드에 빨간 테두리와 메시지가 표시되어 로깅·추적이 쉬워집니다.

3단계 — 프로덕션: 레거시 DATS/TIMS 마이그레이션과 ETag/테스트

기존 테이블이 DATS+TIMS로 일시를 저장하고 있다면, CDS 변환 함수로 UTC 타임스탬프를 파생시킬 수 있습니다(레거시 값이 어느 타임존 기준으로 저장됐는지 반드시 확인해야 합니다).

define view entity ZR_LegacyOrderConv
  as select from zso_legacy
{
  key order_id,
      // DATS+TIMS(서버 타임존 KST 가정) → UTC timestamp
      dats_tims_to_tstmp( created_date,
                          created_time,
                          abap_system_timezone( $session.client, 'NULL' ),
                          $session.client, 'NULL' ) as CreatedAtTs
}

주의점 세 가지입니다.

  • 성능: 변환 함수를 WHERE 절 대상 필드에 걸면 인덱스를 타지 못할 수 있습니다. 대량 조회 뷰에서는 변환을 최소화하거나 배치로 utclong 컬럼에 실체화하는 방식이 권장됩니다
  • 동시성: etag master LastChangedAt은 utclong처럼 정밀도 높은 타입일수록 충돌 감지가 정확해집니다. DATS 필드를 ETag로 쓰면 같은 날 두 번 수정 시 충돌을 놓칩니다
  • 테스트: 단위 테스트에서 utclong_current( )를 직접 호출하지 말고 시각 제공자를 인터페이스로 분리해 목(Mock) 주입하면 "자정 경계", "타임존 경계" 케이스를 재현할 수 있습니다
INTERFACE zif_clock.
  METHODS now RETURNING VALUE(rv_ts) TYPE utclong.
ENDINTERFACE.
" 테스트에서: rv_ts = utclong_add( base, days = -1 ) 등으로 경계 재현

⚠️ 흔한 실수와 트러블슈팅 FAQ

Q1. 생성일시를 DATS+TIMS 두 필드로 저장했더니 해외 법인에서 시간이 다르게 보입니다.

A. DATS/TIMS에는 타임존 정보가 없어서 저장 당시 서버(또는 사용자) 로컬 값이 그대로 보입니다. utclong으로 전환하고 표시 시점에만 CONVERT UTCLONG ... TIME ZONE으로 변환하세요. OData v4에서는 utclong이 DateTimeOffset으로 노출되어 클라이언트가 알아서 변환합니다.

Q2. 납기일을 utclong으로 바꿨더니 오히려 하루 밀려 보입니다.

A. 업무 날짜를 시점 타입으로 저장하면 UTC 변환 과정에서 날짜가 넘어갈 수 있습니다(KST 07/22 00:00 → UTC 07/21 15:00). 달력상 합의된 날짜는 DATS가 정답입니다. 타입을 "업그레이드"한다고 무조건 좋아지는 게 아닙니다.

Q3. 클라우드 환경에서 sy-datum을 썼더니 구문 검사 오류가 납니다.

A. ABAP for Cloud Development에서는 cl_abap_context_info=>get_system_date( ), get_user_time_zone( ), utclong_current( )를 사용해야 합니다.

Q4. timestampl과 utclong 중 무엇을 써야 하나요?

A. 새 개발이고 ABAP 7.54+라면 utclong이 일반적으로 권장됩니다. 내장 연산(utclong_add, utclong_diff)과 타입 안전성이 장점입니다. 구버전 호환이 필요하면 timestampl을 유지하되 산술 연산 직접 계산은 피하세요.

🚀 이후 확장 주제

날짜/시간 타입을 잡았다면 다음으로 넓혀볼 만한 주제들입니다.

  • RAP Draft 테이블의 %admin 타임스탬프 구조와 ETag 상호작용
  • CDS 세션 변수($session.system_date, user_timezone)를 활용한 타임존 인지 뷰
  • XCO 라이브러리(xco_cp_time)를 이용한 날짜 계산 유틸리티
  • Fiori Elements에서 DateTimeOffset 필드의 표시 포맷 제어

📚 함께 보면 좋은 문서

댓글 0

아직 댓글이 없습니다.