[JavaScript] Luxon을 써보기 전에

telnet turtle·2022년 9월 30일
0
post-thumbnail

(이 글은 2020년 10월에 쓰였습니다. 약간 업데이트했지만, 최신 홈페이지의 내용과는 일부 다를 수 있습니다.)

 

들어가며

개발에 필요한 모든 API를 다 외우기는 어렵다. (2년 전에) 리액트 프로젝트에 사용하고 있던 Moment.js 홈페이지에 API를 보러 갔다가 새로 생긴 탭 메뉴를 확인했다.

참고로 Moment.js 라이브러리는 지금 deprecated 되었다고 한다. https://grepper.tistory.com/65

 

자바스크립트 라이브러리인 Moment.js는 datetime 처리에 널리 쓰인다. 그리고 웹팩의 코드 스플리팅을 설명하는 블로그 글에 예제로도 자주 나온다. 아마 코드 크기가 커다래서 그런 것 같다. Moment.js 홈페이지는 다음과 같이 아주 정직한 소개문구를 써놓았다.

Parse, validate, manipulate, and display dates and times in JavaScript.

그럼 Luxon은 어떨까?

A powerful, modern, and friendly wrapper for Javascript dates and times.

요새 트렌드를 따르면, 모던하다는 것은 "API가 현대적이며, 선언적이며 함수형 프로그래밍이 가능하고, 새로운 개념을 제시할지도 모른다" 라는걸 뜻한다. (대략 2018년에서 20년 사이를 말함.)

자바스크립트 객체의 래퍼는 뭘 말할까? Moment.js는 래퍼가 아니란 말인가? 그것도 래퍼가 맞다. momentObject["_d"] 하면 Date 객체가 나온다. 하지만 단순한 래퍼라고 보기에는 이런 저런 메서드도 많이 존재하고, 그냥 다른 객체라고 생각된다. (맨날 못 외워서 API도 홈페이지 보러 가고…) Luxon은 메서드가 좀 적었으면 좋겠다.

 

계속해서 읽어보면…

Features

  • DateTime, Duration, and Interval types.
  • Immutable, chainable, unambiguous API.
  • Parsing and formatting for common and custom formats.
  • Native time zone and Intl support (no locale or tz files).

Datetime과 Duration 모두 꼭 필요한 타입이다. Moment.js를 쓸 때 Duration을 쓰는데 처음에는 굉장히 헷갈렸다. 솔직히 문서를 읽어도 헷갈린다. Interval은 써본 적이 없어서 모르겠다.

Immutable, chainable, unambiguous API도 개발자에게 너무 좋은 것들이다.

Parsing and formatting이 잘 되면 당연히 너무 좋다.

“네이티브 타임존과 Intl 서포트, 로케일과 타임존 파일 없음”. 기억상으로, Moment.js의 로케일과 타임존 지원 파일 용량이 좀 크다고 알고 있다. 그렇기 때문에 웹팩의 특성을 이용해서 트리 셰이킹이 가능하도록 Moment.js를 import하는 방법 같은 글도 봤었다. 시간을 다룰 때 타임존 고려는 거의 반드시 들어갈 텐데, 너무 좋은 특성이다. Intl은 자바스크립트 내장 라이브러리 Intl을 말하는 것이다. 국제화를 위한 내장 JS 기능이다. 코딩애플의 이 동영상을 보면 꽤 쓸만해보인다.

Luxon은 왜 존재하는가?

Why does Luxon exist? 홈페이지에 있는 문서다. 왜 Luxon을 써야 하는가? 만든 의도가 드러난다. 정리하자면 다음과 같다.

  • 관리자 중 하나는 Moment.js를 개선하기 위한 많은 아이디어가 있었지만 Moment.js 코드베이스는 구현하기에 좋지 못하다 느꼈고,
  • 하지만 Moment.js를 배격하는 것은 아니며,
  • 현대적인 Moment.js를 만들고 싶었다.

라고 한다.

 

Luxon의 핵심 아이디어들은 이렇다.

  1. "기본적인 체이너블한 날짜 래퍼"는 유지하고
  2. 모든 타입을 변경불가능하게 하고
  3. API를 명시적으로 만들고, (다른 API는 다른 일을 하고, 잘 정의된 옵션을 갖춘다.)
  4. 국제화와 타임존 지원을 위해 Intl API를 사용하며
  5. 그 밖에 듀레이션, 인터벌 등등이 있다.

 

이것들로 다음과 같은 큰 이점들을 가진다. Luxon을 사용한 코드를 이해하고 디버깅하기 크게 쉬워지며, 네이티브 브라우저 기능을 국제화에 활용해서 훨씬 나은 동작과 굉장히 쉬워진 유지보수를 가능케 하며, JS 날짜 라이브러리 중에 가장 좋은 타임존 서프트를 갖고, Luxon의 듀레이션은 유연하면서 쓰기 쉽고, 등등.

 

만약 브라우저가 Intl API를 지원하지 않는다면 타임존 지원은 어떻게 되는 걸까? 최신 브라우저에서만 된다고 하는데? 현대 브라우저의 기능을 쓴단 것은 프로그래머에게 복잡성을 갖다준다고 한다. 그리고 국제화된 문자열을 코드 베이스에 포함하지 않으므로 그걸 브라우저가 지원하길 기다려야 한다고도 한다. 그리고 Intl API의 몇몇 관점은 브라우저에 종속적이라고 한다. 이걸 쓸 수 있긴 한건가 의문이 들었었지만, Intl 객체가 등장한지도 몇 년이 지난 지금은 딱히 걱정되지 않는다. 오히려 Intl 객체를 이용하는 것은 장점이라고 생각된다. 네이티브 객체를 사용하는 만큼 가벼워질 수 있기 때문이다.

 

Luxon은 Moment.js완 API 컨벤션이 크게 다르고 다른 라이브러리처럼 느껴진다고 하며 그렇기에 "Moment 3.0"은 아니라 한다. 그리고 향후 계획은 어떻게 할지 모르겠다고 한다. 나머지 내용은 홈페이지에 가서 직접 읽어보자.

 

 

시작하기

Yarn v1에서 설치하고 ES6에서 임포트하려면 다음과 같이 한다.

yarn add luxon
import { DateTime } from 'luxon';

Node.js라면 이렇게 하자.

const { DateTime } = require('luxon');

 

타입스크립트를 사용하고 싶으면?

yarn add @types/luxon -D

나머지 내용은 인스톨 가이드를 읽어보자.

 

 

다음으로는 퀵 투어를 읽어보았다. 일단 오브젝트를 만들어보자. DateTIme은 밀리세컨드 시각을 나타내며, 타임존과 로케일을 갖고 있다. 특정 시각과 현재 시각을 나타내는 오브젝트는 다음과 같이 만든다.

const dt = DateTime.local(2020, 1, 31, 10, 45); // 2020-01-31 10:45
const now = DateTime.local(); // now

오브젝트와 ISO스트링을 사용해서 만들 수 도 있다.

DateTime.fromObject({ hour: 10, minute: 45, second: 6 }) //~> today at 10:45:06
DateTime.fromObject({ hour: 10, minute: 45, second: 6, zone: 'utc' })

DateTime.fromISO("2020-01-31")          //=> 31 Jan 2020 at midnight
DateTime.fromISO("2017-01-31T10:45:00") //=> 31 Jan 2020 at 8:30

퀵 투어의 나머지 문서에서는 불변성, 연산, 세트, Intl, 타임존, Duration, Interval에 대한 내용이 나와있다.

 

 

Moment 유저들을 위한 문서

자바스크립트의 네이티브 객체 Date보다 Moment.js를 많이 사용했기 때문에 For Moment users를 안 읽을 수가 없었다.

Moment.js의 #add는 객체 속성을 변경한다. Luxon의 #plus는 새 객체를 리턴한다. 원래 객체는 바뀌지 않는다. 아주 마음에 든다. 세터를 사용하면 원래 객체를 바꿀 수 있다. API가 명료하다고 느껴진다.

const d = DateTime.local();
const d1 = d.plus({ hours: 1 });
d.valueOf() === d1.valueOf(); //=> false

세터는 다음과 같이 쓴다.

d.set({ hour: 10, minute: 45 })

몇 가지 Moment.js와 다른 주요 기능이 있다. 달은 0부터 시작하지 않고 1부터 시작한다. 이제 1월은 1이다. 네이티브 Date와도 다른 점이다. DurationInterval 타입이 둘 다 있다.

API 스타일 차이점이다.

  1. Luxon 메서드는 마지막 파라미터로 옵션 오브젝트를 받기도 한다.
  2. Luxon 오브젝트를 생성하기 위해 다른 스태틱 메서드를 쓰기도 한다. (예: fromISO.) 이는 Moment.js가 하나의 함수로 입력에 기반해서 오브젝트를 생성한 것과 다르다. (moment()를 말한다.)
  3. Luxon의 파서는 매우 엄격하며 Moment.js의것은 더 허술하다.
  4. Luxon은 액세서 메서드 대신 게터를 사용하므로, datetime.year() 대신 datetime.year이다.
  5. Luxon은 세터를 중앙화했기 때문에 Moment.js처럼 datetime.year(2020).month(1) 대신 datetime.set({ year: 2020, month: 1 })이다.
  6. Luxon의 Duration은 분리된 탑레벨 클래스다. (Datetime과 분리된다.)
  7. Luxon의 메서드의 파라미터는 자동으로 Luxon 인스턴스로 변환되지 않는다. 따라서 Moment.js의 m.diff('2018-05-30)은 Luxon에서 dt.diff(Datetime.fromISO('2018-05-30'))이다.

4번은 내게 바로 와닿는 변경점이다. Moment.js의 datetime.year()는 세터인지 게터인지 모호한 측면이 있었다. 명시적으로 게터와 세터를 두는 것이 나에겐 좋다. (다만 의문점은 게터가 액세스 메서드와 똑같은 게 아닌가? 하지만 요지는 멤버에 직접 접근해서 값을 얻는다는 것이다. 이게 요새 추세에 더 맞는다고 느낀다. Java와 Kotlin의 클래스 컨벤션 차이처럼..)

Moment.js엔 게터와 세터 함수가 이름이 같은 경우가 있다. 예를 들면 여기에서 확인할 수 있는 moment().year(Number); /* set year */ moment().year() /* get year *다. 나쁜건지는 모르겠지만 어쨌든 내 마음에는 들지 않는 디자인이다. Moment.js를 사용한 코드에서 #year를 읽었을 때 이게 게터인지 세터인지 헷갈린 적이 분명 있었다.

Moment.js보다 또 나은 점 하나는 바로 #plus메서드다. m.add(1, 'hours')dt.plus({ hours: 1 }) 당연히 후자가 낫다. 전자는 파라미터로 문자열로 입력해야 하는 단점이 있고, 1시간 15분을 더하려면 메서드를 두 번 호출해야 하는 단점도 있다. 객체 호출 맘에 든다.

 

써보기

이만큼 알아봤으면 이제 실제로 써볼 차례지만, 지금 datetime을 다룰 만한 프로젝트가 없다. 실제로 써 봐야 어떤 문제가 발생하는지 알게 되지 그렇지 않으면 알 수가 없다. 사용해보자.

 

쓰지 않은 이유

회사에서 새로 판 프로젝트에 Moment.js대신 Luxon을 넣는걸 얘기해본 적이 있었지만 실제로는 넣지 않고 Moment.js를 다시 사용했다. 거기에는 몇 가지 이유가 있었다.

첫 번째로, Moment.js를 반드시 쓰지 않아야 하는 이유는 없었다.

두 번째, 나를 포함해 JS 개발자들이 모두 Moment.js에 익숙했다.

세 번째, 이미 프로젝트에 쓰고 있는 차트 라이브러리에 Moment.js 의존성이 있는걸 발견해서 굳이 더 나아가지 않았다. (yarn.lock 파일에서 확인했다.)

마지막으로, 아직 실험적인 프로젝트라고 여겨져서 프로덕션에 집어넣는건 필요한 걸 따져보는 시간이 충분치 못했다. 이런 건 개인 프로젝트에서 먼저 사용해보는 것도 좋다. 실제로 사용해보며 장단점을 파악하기에 좋기 때문이다.

 

2020. 10. 4.

profile
프론트엔드 엔지니어

1개의 댓글

comment-user-thumbnail
2023년 3월 8일

여러 포인트를 깔끔히 잘 정리해주셔서 잘 읽고 갑니다:)

답글 달기