DateTime 조작에 대한 확실한 가이드
게시 됨: 2022-03-11소프트웨어 개발자로서 날짜 조작에서 벗어날 수 없습니다. 개발자가 빌드하는 거의 모든 앱에는 사용자로부터 날짜/시간을 가져와 데이터베이스에 저장하고 사용자에게 다시 표시해야 하는 구성 요소가 있습니다.
프로그래머에게 날짜와 시간대를 처리한 경험에 대해 물어보면 아마도 전쟁 이야기를 나눌 것입니다. 날짜 및 시간 필드를 처리하는 것은 확실히 로켓 과학이 아니지만 종종 지루하고 오류가 발생하기 쉽습니다.
이 주제에 대한 수백 개의 기사가 있지만 대부분은 너무 학술적이어서 핵심적인 세부 사항에 초점을 맞추거나 너무 엉성하여 많은 설명 없이 짧은 코드 스니펫을 제공합니다. DateTime 조작에 대한 이 심층 가이드는 이 주제에 대한 정보의 바다를 탐색하지 않고도 시간 및 날짜와 관련된 프로그래밍 개념과 모범 사례를 이해하는 데 도움이 됩니다.
이 기사에서는 날짜 및 시간 필드에 대해 명확하게 생각하도록 돕고 날짜/시간 지옥을 피하는 데 도움이 될 수 있는 몇 가지 모범 사례를 제안합니다. 여기에서 날짜 및 시간 값을 올바르게 조작하는 데 필요한 몇 가지 주요 개념, DateTime 값을 저장하고 API를 통해 전송하는 데 편리한 형식 등을 살펴보겠습니다.
우선, 프로덕션 코드에 대한 정답은 거의 항상 자체 라이브러리를 사용하는 것보다 적절한 라이브러리를 사용하는 것입니다. 이 기사에서 논의된 DateTime 계산의 잠재적인 어려움은 빙산의 일각에 불과하지만 라이브러리가 있든 없든 알면 여전히 도움이 됩니다.
DateTime 라이브러리는 올바르게 이해하면 도움이 됩니다.
날짜 라이브러리는 삶을 더 쉽게 만드는 데 여러 면에서 도움이 됩니다. 날짜 구문 분석, 날짜 산술 및 논리 연산, 날짜 형식을 크게 단순화합니다. 프론트 엔드와 백 엔드 모두에 대한 신뢰할 수 있는 날짜 라이브러리를 찾아 대부분의 힘든 작업을 수행할 수 있습니다.
그러나 날짜/시간이 실제로 어떻게 작동하는지 생각하지 않고 날짜 라이브러리를 사용하는 경우가 많습니다. 날짜/시간은 복잡한 개념입니다. 잘못된 이해로 인해 발생하는 버그는 날짜 라이브러리의 도움을 받아도 이해하고 수정하기가 매우 어려울 수 있습니다. 프로그래머는 기본 사항을 이해하고 날짜 라이브러리가 이를 최대한 활용하기 위해 해결하는 문제를 이해할 수 있어야 합니다.
또한 날짜/시간 라이브러리는 지금까지만 사용할 수 있습니다. 모든 날짜 라이브러리는 DateTime을 나타내는 편리한 데이터 구조에 대한 액세스를 제공하여 작동합니다. REST API를 통해 데이터를 보내고 받는 경우 JSON에는 DateTime을 나타내는 기본 데이터 구조가 없기 때문에 결국 날짜를 문자열로 또는 그 반대로 변환해야 합니다. 여기에 설명된 개념은 이러한 날짜-문자열 및 문자열-날짜 변환을 수행할 때 발생할 수 있는 몇 가지 일반적인 문제를 방지하는 데 도움이 됩니다.
참고: 이 기사에서 설명하는 프로그래밍 언어로 JavaScript를 사용했지만 이는 거의 모든 프로그래밍 언어와 해당 날짜 라이브러리에 광범위하게 적용되는 일반적인 개념입니다. 따라서 이전에 JavaScript 한 줄을 작성한 적이 없더라도 이 기사에서 JavaScript에 대한 사전 지식이 거의 없다고 가정하므로 계속 읽으십시오.
시간 표준화
DateTime은 매우 특정한 시점입니다. 이것에 대해 생각해 봅시다. 내가 이 글을 쓸 때 내 노트북의 시계는 7월 21일 오후 1시 29분을 가리키고 있다. 이것은 우리가 "현지 시간"이라고 부르는 시간입니다. 제 주변의 벽시계와 손목시계에서 볼 수 있는 시간입니다.
몇 분 정도 시간을 내어 친구에게 오후 3시에 가까운 카페에서 만나자고 하면 대략 그 시간에 그곳에서 만날 것으로 예상할 수 있습니다. 마찬가지로, 예를 들어 "1시간 30분 후에 만나자"라고 대신 말하면 혼란이 없을 것입니다. 우리는 같은 도시나 시간대에 사는 사람들과 일상적으로 이런 식으로 시간에 대해 이야기합니다.
다른 시나리오를 생각해 봅시다. 스웨덴 웁살라에 사는 친구에게 오후 5시에 이야기하고 싶다고 말하고 싶습니다. 나는 그에게 "안톤, 오후 5시에 이야기합시다"라는 메시지를 보냅니다. 나는 즉시 "당신의 시간입니까, 아니면 제 시간입니까?"라는 응답을 받습니다.
Anton은 UTC+01:00인 중부 유럽 시간대에 살고 있다고 말합니다. 저는 UTC+05:45에 살고 있습니다. 이것은 내가 살고 있는 곳이 오후 5시일 때 오후 5시 - 05:45 = 오전 11:15 UTC라는 것을 의미하며, 이는 웁살라에서 오전 11:15 UTC + 01:00 = 오후 12:15로 변환되며 둘 다에 완벽합니다. 우리의.
또한 표준 시간대(중앙 유럽 시간)와 표준 시간대 오프셋(UTC+05:45)의 차이에 유의하십시오. 국가는 정치적인 이유로 일광 절약 시간제에 대한 표준 시간대 오프셋을 변경하기로 결정할 수도 있습니다. 거의 매년 하나 이상의 국가에서 규칙이 변경됩니다. 즉, 이러한 규칙이 적용된 모든 코드는 최신 상태로 유지되어야 합니다. 즉, 앱의 각 계층에 대해 코드베이스가 이에 의존하는 것을 고려해 볼 가치가 있습니다.
이것이 대부분의 경우 프런트 엔드에서만 표준 시간대를 처리하도록 권장하는 또 다른 좋은 이유입니다. 그렇지 않은 경우 데이터베이스 엔진이 사용하는 규칙이 프런트 엔드 또는 백엔드의 규칙과 일치하지 않으면 어떻게 됩니까?
사용자와 보편적으로 허용되는 표준에 따라 두 가지 다른 버전의 시간을 관리하는 이 문제는 정밀도가 핵심이고 1초라도 큰 차이를 만들 수 있는 프로그래밍 세계에서는 더욱 어렵습니다. 이러한 문제를 해결하기 위한 첫 번째 단계는 DateTime을 UTC로 저장하는 것입니다.
형식 표준화
UTC 시간만 저장하면 되며 사용자의 시간대를 알고 있는 한 항상 사용자의 시간으로 변환할 수 있기 때문에 시간을 표준화하는 것은 훌륭합니다. 반대로 사용자의 현지 시간과 시간대를 알고 있으면 이를 UTC로 변환할 수 있습니다.
그러나 날짜와 시간은 다양한 형식으로 지정할 수 있습니다. 날짜로 "7월 30일" 또는 "7월 30일" 또는 "7/30"(또는 30/7, 거주 지역에 따라 다름)이라고 쓸 수 있습니다. 시간에 대해 "9:30 PM" 또는 "2130"이라고 쓸 수 있습니다.
전 세계의 과학자들이 이 문제를 해결하기 위해 함께 모여 프로그래머가 정말 좋아하는 시간을 설명하는 형식을 결정했습니다. 시간은 짧고 정확하기 때문입니다. ISO-8601 확장 형식의 단순화된 버전인 "ISO 날짜 형식"이라고 하며 다음과 같습니다.
00:00 또는 UTC의 경우 UTC의 다른 이름인 Zulu 시간을 의미하는 "Z"를 대신 사용합니다.
JavaScript의 날짜 조작 및 산술
모범 사례를 시작하기 전에 구문과 일반 개념을 이해하기 위해 JavaScript를 사용한 날짜 조작에 대해 알아보겠습니다. JavaScript를 사용하지만 이 정보를 좋아하는 프로그래밍 언어에 쉽게 적용할 수 있습니다.
우리는 날짜 산술을 사용하여 대부분의 개발자가 겪는 일반적인 날짜 관련 문제를 해결할 것입니다.
내 목표는 문자열에서 날짜 개체를 만들고 구성 요소를 추출하는 것을 편안하게 만드는 것입니다. 이것은 날짜 라이브러리가 도움이 될 수 있는 것입니다. 그러나 항상 그것이 뒤에서 어떻게 수행되는지 이해하는 것이 더 좋습니다.
날짜/시간으로 손을 더럽히면 직면한 문제에 대해 생각하고 모범 사례를 추출하고 앞으로 나아가기가 더 쉽습니다. 모범 사례로 건너뛰고 싶다면 자유롭게 하되 최소한 아래의 날짜 산술 섹션을 훑어보는 것이 좋습니다.
자바스크립트 날짜 객체
프로그래밍 언어에는 우리의 삶을 더 쉽게 만들어주는 유용한 구조가 포함되어 있습니다. JavaScript Date
객체가 그러한 것 중 하나입니다. 현재 날짜 및 시간을 가져오고, 날짜를 변수에 저장하고, 날짜 산술을 수행하고, 사용자의 로케일을 기반으로 날짜 형식을 지정하는 편리한 방법을 제공합니다.
브라우저 구현의 차이와 일광 절약 시간(DST)의 잘못된 처리로 인해 미션 크리티컬 애플리케이션에 대해 Date 객체에 의존하는 것은 권장되지 않으며 아마도 Luxon, date-fns 또는 dayjs와 같은 DateTime 라이브러리를 사용해야 할 것입니다. (무엇을 사용하든 한때 인기 있었던 Moment.js(코드에 나타나는 것처럼 단순히 moment
라고 함)는 이제 사용되지 않으므로 피하십시오.)
그러나 교육 목적으로 JavaScript가 DateTime을 처리하는 방법을 배우기 위해 Date() 객체가 제공하는 메서드를 사용할 것입니다.
현재 날짜 가져오기
const currentDate = new Date();
Date 생성자에 아무 것도 전달하지 않으면 반환된 날짜 개체에 현재 날짜와 시간이 포함됩니다.
그런 다음 다음과 같이 날짜 부분만 추출하도록 형식을 지정할 수 있습니다.
const currentDate = new Date(); const currentDayOfMonth = currentDate.getDate(); const currentMonth = currentDate.getMonth(); // Be careful! January is 0, not 1 const currentYear = currentDate.getFullYear(); const dateString = currentDayOfMonth + "-" + (currentMonth + 1) + "-" + currentYear; // "27-11-2020"
참고: "1월은 0"이라는 함정은 일반적이지만 보편적인 것은 아닙니다. 사용을 시작하기 전에 모든 언어(또는 구성 형식: 예를 들어 cron은 특히 1 기반)에 대한 문서를 다시 확인하는 것이 좋습니다.
현재 타임스탬프 가져오기
대신 현재 타임스탬프를 가져오려면 새 Date 객체를 만들고 getTime() 메서드를 사용할 수 있습니다.
const currentDate = new Date(); const timestamp = currentDate.getTime();
JavaScript에서 타임스탬프는 1970년 1월 1일 이후로 경과된 밀리초 수입니다.
<IE8을 지원하지 않으려면 Date.now()
를 사용하여 새 Date 객체를 만들 필요 없이 직접 타임스탬프를 가져올 수 있습니다.
날짜 구문 분석
문자열을 JavaScript 날짜 객체로 변환하는 것은 다른 방식으로 수행됩니다.
Date 객체의 생성자는 다양한 날짜 형식을 허용합니다.
const date1 = new Date("Wed, 27 July 2016 13:30:00"); const date2 = new Date("Wed, 27 July 2016 07:45:00 UTC"); const date3 = new Date("27 July 2016 13:30:00 UTC+05:45");
JS는 모든 날짜의 요일을 결정할 수 있으므로 요일을 포함할 필요가 없습니다.
년, 월, 일, 시, 분, 초를 별도의 인수로 전달할 수도 있습니다.
const date = new Date(2016, 6, 27, 13, 30, 0);
물론 항상 ISO 날짜 형식을 사용할 수 있습니다.
const date = new Date("2016-07-27T07:45:00Z");
그러나 시간대를 명시적으로 제공하지 않으면 문제가 발생할 수 있습니다!
const date1 = new Date("25 July 2016"); const date2 = new Date("July 25, 2016");
둘 중 하나는 2016년 7월 25일 00:00:00 현지 시간을 제공합니다.
ISO 형식을 사용하면 시간과 시간대를 지정하지 않고 날짜만 지정해도 자동으로 시간대를 UTC로 받아들입니다.
이는 다음을 의미합니다.
new Date("25 July 2016").getTime() !== new Date("2016-07-25").getTime() new Date("2016-07-25").getTime() === new Date("2016-07-25T00:00:00Z").getTime()
날짜 형식 지정
다행스럽게도 최신 JavaScript에는 날짜 형식 지정을 간단한 작업으로 만드는 몇 가지 편리한 국제화 기능이 표준 Intl
네임스페이스에 내장되어 있습니다.
이를 위해 우리는 출력 기본 설정으로 초기화된 Date
및 Intl.DateTimeFormat
이라는 두 개의 개체가 필요합니다. 미국식(M/D/YYYY) 형식을 사용한다고 가정하면 다음과 같습니다.
const firstValentineOfTheDecade = new Date(2020, 1, 14); // 1 for February const enUSFormatter = new Intl.DateTimeFormat('en-US'); console.log(enUSFormatter.format(firstValentineOfTheDecade)); // 2/14/2020
대신 네덜란드어(D/M/YYYY) 형식을 원하면 다른 문화권 코드를 DateTimeFormat
생성자에 전달하면 됩니다.
const nlBEFormatter = new Intl.DateTimeFormat('nl-BE'); console.log(nlBEFormatter.format(firstValentineOfTheDecade)); // 14/2/2020
또는 월 이름을 철자로 표기한 더 긴 형태의 미국식 형식:
const longEnUSFormatter = new Intl.DateTimeFormat('en-US', { year: 'numeric', month: 'long', day: 'numeric', }); console.log(longEnUSFormatter.format(firstValentineOfTheDecade)); // February 14, 2020
이제 날짜에 적절한 서수 형식, 즉 "14" 대신 "14"를 원하면 불행히도 약간의 해결 방법이 필요합니다. 왜냐하면 이 글을 쓰는 시점에서 day
의 유효한 값은 "numeric"
또는 "2-digit"
. 이를 위해 Intl
의 다른 부분을 활용하기 위해 Flavio Copes 버전의 Mathias Bynens' 코드를 차용하면 formatToParts()
를 통해 요일 출력을 사용자 지정할 수 있습니다.
const pluralRules = new Intl.PluralRules('en-US', { type: 'ordinal' }) const suffixes = { 'one': 'st', 'two': 'nd', 'few': 'rd', 'other': 'th' } const convertToOrdinal = (number) => `${number}${suffixes[pluralRules.select(number)]}` // At this point: // convertToOrdinal("1") === "1st" // convertToOrdinal("2") === "2nd" // etc. const extractValueAndCustomizeDayOfMonth = (part) => { if (part.type === "day") { return convertToOrdinal(part.value); } return part.value; }; console.log( longEnUSFormatter.formatToParts(firstValentineOfTheDecade) .map(extractValueAndCustomizeDayOfMonth) .join("") ); // February 14th, 2020
불행히도 formatToParts
는 이 글을 쓰는 시점에서 Internet Explorer(IE)에서 전혀 지원되지 않지만 다른 모든 데스크톱, 모바일 및 백엔드(예: Node.js) 기술은 지원합니다. IE를 지원해야 하고 서수가 절대적으로 필요한 사람들을 위해 아래의 사이드노트(또는 더 나은 적절한 날짜 라이브러리)가 답을 제공합니다.
버전 11 이전의 IE와 같은 이전 브라우저를 지원해야 하는 경우 Python 또는 PHP에는 strftime
과 같은 표준 날짜 형식 지정 기능이 없기 때문에 JavaScript의 날짜 형식 지정은 더 어렵습니다.
예를 들어 PHP에서 strftime("Today is %b %d %Y %X", mktime(5,10,0,12,30,99))
는 Today is Dec 30 1999 05:10:00
을 제공합니다.
%
가 앞에 오는 다른 문자 조합을 사용하여 날짜를 다른 형식으로 가져올 수 있습니다. (모든 언어가 각 문자에 동일한 의미를 할당하는 것은 아닙니다. 특히 'M'과 'm'은 분과 월로 바뀔 수 있습니다.)
사용하려는 형식이 확실하다면 위에서 다룬 JavaScript 함수를 사용하여 개별 비트를 추출하고 직접 문자열을 만드는 것이 가장 좋습니다.
var currentDate = new Date (); var date = currentDate.getDate(); var month = currentDate.getMonth(); var year = currentDate.getFullYear();
MM/DD/YYYY 형식의 날짜를 다음과 같이 얻을 수 있습니다.
var monthDateYear = (month+ 1 ) + "/" + date + "/" + year;
이 솔루션의 문제는 일부 월과 요일이 한 자리 숫자이고 다른 날짜는 두 자리이기 때문에 날짜에 일관성 없는 길이를 줄 수 있다는 것입니다. 예를 들어 테이블 열에 날짜를 표시하는 경우 날짜가 정렬되지 않기 때문에 문제가 될 수 있습니다.
선행 0을 추가하는 "패드" 기능을 사용하여 이 문제를 해결할 수 있습니다.
function pad ( n ) { return n< 10 ? '0' +n : n; }
이제 다음을 사용하여 MM/DD/YYYY 형식의 올바른 날짜를 얻습니다.
var mmddyyyy = pad(month + 1 ) + "/" + pad(date) + "/" + year;
대신 DD-MM-YYYY를 원하는 경우 프로세스는 유사합니다.
var ddmmyyyy = pad(date) + "-" + pad(month + 1 ) + "-" + year;
이제 "월, 연도" 형식으로 날짜를 인쇄해 보겠습니다. 이름에 대한 월 인덱스 매핑이 필요합니다.
var monthNames = [ "January" , "February" , "March" , "April" , "May" , "June" , "July" , "August" , "September" , "October" , "November" , "December" ]; var dateWithFullMonthName = monthNames[month] + " " + pad(date) + ", " + year;
어떤 사람들은 날짜를 2013년 1월 1일로 표시하고 싶어합니다. 문제 없습니다. 1에 대해 1, 12에 12, 103에 대해 103 등을 반환하는 도우미 함수 ordinal
있으면 되고 나머지는 간단합니다.
var ordinalDate = ordinal(date) + " " + monthNames[month] + ", " + year;
날짜 개체에서 요일을 결정하는 것은 쉽기 때문에 다음과 같이 추가해 보겠습니다.
var daysOfWeek = [ "Sun" , "Mon" , "Tue" , "Wed" , "Thu" , "Fri" , "Sat" ]; ordinalDateWithDayOfWeek = daysOfWeek[currentDate.getDay()] + ", " + ordinalDate;
여기서 더 중요한 점은 날짜에서 숫자를 추출하면 형식이 대부분 문자열과 관련이 있다는 것입니다.
날짜 형식 변경
날짜를 구문 분석하고 형식을 지정하는 방법을 알고 나면 날짜를 한 형식에서 다른 형식으로 변경하는 것은 두 가지를 결합하는 문제입니다.
예를 들어 2013년 7월 21일 형식의 날짜가 있고 형식을 21-07-2013으로 변경하려는 경우 다음과 같이 수행할 수 있습니다.
const myDate = new Date("Jul 21, 2013"); const dayOfMonth = myDate.getDate(); const month = myDate.getMonth(); const year = myDate.getFullYear(); function pad(n) { return n<10 ? '0'+n : n } const ddmmyyyy = pad(dayOfMonth) + "-" + pad(month + 1) + "-" + year; // "21-07-2013"
JavaScript Date 객체의 현지화 기능 사용하기
위에서 논의한 날짜 형식 지정 방법은 대부분의 응용 프로그램에서 작동해야 하지만 실제로 날짜 형식을 현지화하려면 Date
객체의 toLocaleDateString()
메서드를 사용하는 것이 좋습니다.
const today = new Date().toLocaleDateString('en-GB', { day: 'numeric', month: 'short', year: 'numeric', });
... 26 Jul 2016
일과 같은 것을 제공합니다.
로케일을 'en-US'로 변경하면 대신 "2016년 7월 26일"이 표시됩니다. 서식이 어떻게 변경되었지만 표시 옵션은 여전히 동일하게 유지되어 매우 유용한 기능인 것을 알 수 있습니다. 이전 섹션에서 볼 수 있듯이 새로운 Intl.DateTimeFormat
기반 기술은 이와 매우 유사하게 작동하지만 옵션을 한 번만 설정하면 되도록 포맷터 개체를 재사용할 수 있습니다.
toLocaleDateString()
을 사용하면 컴퓨터에서 출력이 괜찮아 보이더라도 항상 형식 지정 옵션을 전달하는 것이 좋습니다. 이렇게 하면 월 이름이 정말 긴 예기치 않은 로케일이 깨지거나 짧은 이름으로 인해 어색해 보이는 UI를 보호할 수 있습니다.

대신 "7월" 한 달 전체를 원하면 옵션의 월 매개변수를 "long"으로 변경하기만 하면 됩니다. JavaScript는 나를 위해 모든 것을 처리합니다. en-US의 경우 이제 2016년 7월 26일을 얻습니다.
참고: 브라우저가 자동으로 사용자의 로케일을 사용하도록 하려면 "undefined"를 첫 번째 매개변수로 전달할 수 있습니다.
날짜의 숫자 버전을 표시하고 다른 로케일에 대해 MM/DD/YYYY 대 DD/MM/YYYY를 사용하지 않으려면 다음과 같은 간단한 솔루션을 제안합니다.
const today = new Date().toLocaleDateString(undefined, { day: 'numeric', month: 'numeric', year: 'numeric', });
내 컴퓨터에서 이것은 7/26/2016
을 출력합니다. 월과 날짜가 두 자리 숫자인지 확인하려면 옵션을 변경하면 됩니다.
const today = new Date().toLocaleDateString(undefined, { day: '2-digit', month: '2-digit', year: 'numeric', });
07/26/2016
을 출력합니다. 우리가 원했던 바로 그것!
다른 관련 기능을 사용하여 시간과 날짜가 모두 표시되는 방식을 현지화할 수도 있습니다.
암호 | 산출 | 설명 |
---|---|---|
| "오전 4시 21분 38초" | 시간의 현지화 버전 표시 |
| "오전 04:21:38" | 제공된 옵션에 따라 현지화된 시간 표시 |
| "2016년 7월 22일, 오전 4시 21분 38초" | 사용자 로케일의 날짜 및 시간 표시 |
| "2016년 7월 22일 오전 4시 21분" | 제공된 옵션에 따라 현지화된 날짜 및 시간 표시 |
상대적 날짜 및 시간 계산
다음은 JavaScript 날짜에 20일을 추가하는 예입니다(즉, 알려진 날짜로부터 20일 후 날짜를 파악).
const myDate = new Date("July 20, 2016 15:00:00"); const nextDayOfMonth = myDate.getDate() + 20; myDate.setDate(nextDayOfMonth); const newDate = myDate.toLocaleString();
원래 날짜 개체는 이제 7월 20일 이후 20일의 날짜를 나타내고 newDate
에는 해당 날짜를 나타내는 지역화된 문자열이 포함됩니다. 내 브라우저에서 newDate
에는 "8/9/2016, 3:00:00 PM"이 포함되어 있습니다.
전체 날짜보다 더 정확한 차이로 상대 타임스탬프를 계산하려면 Date.getTime()
및 Date.setTime()
을 사용하여 특정 에포크(즉, 1970년 1월 1일) 이후의 밀리초 수를 나타내는 정수로 작업할 수 있습니다. 예를 들어 지금이 17시간 후인지 알고 싶다면:
const msSinceEpoch = (new Date()).getTime(); const seventeenHoursLater = new Date(msSinceEpoch + 17 * 60 * 60 * 1000);
날짜 비교
날짜와 관련된 다른 모든 것과 마찬가지로 날짜 비교에는 고유한 문제가 있습니다.
먼저 날짜 개체를 만들어야 합니다. 다행히 <, >, <= 및 >= 모두 작동합니다. 따라서 2014년 7월 19일과 2014년 7월 18일을 비교하는 것은 다음과 같이 쉽습니다.
const date1 = new Date("July 19, 2014"); const date2 = new Date("July 28, 2014"); if(date1 > date2) { console.log("First date is more recent"); } else { console.log("Second date is more recent"); }
동일한 날짜를 나타내는 두 날짜 개체는 여전히 두 개의 다른 날짜 개체이고 같지 않기 때문에 같음을 확인하는 것은 더 까다롭습니다. 예를 들어 "2014년 7월 20일"과 "2014년 7월 20일"은 동일한 날짜를 나타내지만 다른 문자열 표현을 갖기 때문에 날짜 문자열을 비교하는 것은 좋지 않습니다. 아래 스니펫은 첫 번째 요점을 보여줍니다.
const date1 = new Date("June 10, 2003"); const date2 = new Date(date1); const equalOrNot = date1 == date2 ? "equal" : "not equal"; console.log(equalOrNot);
이것은 not equal
을 출력합니다.
이 특별한 경우는 다음과 같이 날짜(타임스탬프)의 정수 등가물을 비교하여 수정할 수 있습니다.
date1.getTime() == date2.getTime()
이 예를 여러 곳에서 보았지만 일반적으로 다른 날짜 개체에서 날짜 개체를 생성하지 않기 때문에 마음에 들지 않습니다. 그래서 저는 학문적 관점에서만 본 사례가 중요하다고 생각합니다. 또한 이를 위해서는 두 Date 객체가 정확히 동일한 초를 참조해야 하지만, 동일한 날짜, 시간 또는 분을 참조하는지 여부만 알고 싶을 수 있습니다.
좀 더 실용적인 예를 살펴보겠습니다. 사용자가 입력한 생일이 API에서 얻은 행운의 날짜와 같은지 비교하려고 합니다.
const userEnteredString = "12/20/1989"; // MM/DD/YYYY format const dateStringFromAPI = "1989-12-20T00:00:00Z"; const dateFromUserEnteredString = new Date(userEnteredString) const dateFromAPIString = new Date(dateStringFromAPI); if (dateFromUserEnteredString.getTime() == dateFromAPIString.getTime()) { transferOneMillionDollarsToUserAccount(); } else { doNothing(); }
둘 다 같은 날짜를 나타내었지만 불행히도 사용자는 백만 달러를 얻지 못할 것입니다.
여기에 문제가 있습니다. JavaScript는 명시적으로 달리 지정하지 않는 한 항상 브라우저가 제공하는 시간대를 표준 시간대라고 가정합니다.
이것은 나에게 new Date ("12/20/1989")
가 1989-12-20T00:00:00+5:45 또는 1989-12-19T18:15:00Z 날짜를 생성한다는 것을 의미합니다. 1989-12-20T00:00:00Z 타임 스탬프 측면에서.
기존 날짜 개체의 표준 시간대만 변경할 수는 없으므로 이제 우리의 목표는 현지 표준 시간대 대신 UTC를 사용하여 새 날짜 개체를 만드는 것입니다.
날짜 개체를 만드는 동안 사용자의 시간대를 무시하고 UTC를 사용합니다. 두 가지 방법이 있습니다.
- 사용자 입력 날짜에서 ISO 형식의 날짜 문자열을 만들고 이를 사용하여 Date 개체를 만듭니다. 유효한 ISO 날짜 형식을 사용하여 UTC 대 로컬의 의도를 매우 명확하게 하면서 Date 객체를 생성합니다.
const userEnteredDate = "12/20/1989"; const parts = userEnteredDate.split("/"); const userEnteredDateISO = parts[2] + "-" + parts[0] + "-" + parts[1]; const userEnteredDateObj = new Date(userEnteredDateISO + "T00:00:00Z"); const dateFromAPI = new Date("1989-12-20T00:00:00Z"); const result = userEnteredDateObj.getTime() == dateFromAPI.getTime(); // true
이것은 기본적으로 자정(즉, 00:00:00Z)으로 설정되기 때문에 시간을 지정하지 않은 경우에도 작동합니다.
const userEnteredDate = new Date("1989-12-20"); const dateFromAPI = new Date("1989-12-20T00:00:00Z"); const result = userEnteredDate.getTime() == dateFromAPI.getTime(); // true
기억하십시오: 날짜 생성자가 YYYY-MM-DD의 올바른 ISO 날짜 형식으로 된 문자열을 전달받은 경우 자동으로 UTC를 가정합니다.
- JavaScript는 날짜의 UTC 타임스탬프를 가져오는 데 사용할 수 있는 깔끔한 Date.UTC() 함수를 제공합니다. 날짜에서 구성 요소를 추출하여 함수에 전달합니다.
const userEnteredDate = new Date("12/20/1989"); const userEnteredDateTimeStamp = Date.UTC(userEnteredDate.getFullYear(), userEnteredDate.getMonth(), userEnteredDate.getDate(), 0, 0, 0); const dateFromAPI = new Date("1989-12-20T00:00:00Z"); const result = userEnteredDateTimeStamp == dateFromAPI.getTime(); // true ...
두 날짜의 차이점 찾기
마주하게 될 일반적인 시나리오는 두 날짜 간의 차이를 찾는 것입니다.
두 가지 사용 사례에 대해 논의합니다.
두 날짜 사이의 일수 구하기
두 날짜를 모두 UTC 타임스탬프로 변환하고 밀리초 단위의 차이를 찾아 해당 날짜를 찾습니다.
const dateFromAPI = "2016-02-10T00:00:00Z"; const now = new Date(); const datefromAPITimeStamp = (new Date(dateFromAPI)).getTime(); const nowTimeStamp = now.getTime(); const microSecondsDiff = Math.abs(datefromAPITimeStamp - nowTimeStamp); // Math.round is used instead of Math.floor to account for certain DST cases // Number of milliseconds per day = // 24 hrs/day * 60 minutes/hour * 60 seconds/minute * 1000 ms/second const daysDiff = Math.round(microSecondsDiff / (1000 * 60 * 60 * 24)); console.log(daysDiff);
생년월일에서 사용자의 나이 찾기
const birthDateFromAPI = "12/10/1989";
참고: 비표준 형식이 있습니다. API 문서를 읽고 이것이 10월 12일인지 12월 10일인지 확인하십시오. 그에 따라 ISO 형식으로 변경하십시오.
const parts = birthDateFromAPI.split("/"); const birthDateISO = parts[2] + "-" + parts[0] + "-" + parts[1]; const birthDate = new Date(birthDateISO); const today = new Date(); let age = today.getFullYear() - birthDate.getFullYear(); if(today.getMonth() < birthDate.getMonth()) { age--; } if(today.getMonth() == birthDate.getMonth() && today.getDate() < birthDate.getDate()) { age--; }
이 코드를 작성하는 더 간결한 방법이 있다는 것을 알고 있지만 논리의 순전한 명확성 때문에 이 방법을 작성하는 것을 좋아합니다.
데이트 지옥을 피하기 위한 제안
이제 날짜 산술에 익숙해졌으므로 따라야 할 모범 사례와 이를 따라야 하는 이유를 이해할 수 있습니다.
사용자로부터 DateTime 가져오기
사용자로부터 날짜와 시간을 얻는 경우 아마도 로컬 DateTime을 찾고 있을 것입니다. 날짜 산술 섹션에서 Date
생성자가 다양한 방법으로 날짜를 받아들일 수 있음을 보았습니다.
혼란을 없애기 위해 이미 유효한 구문 분석 가능한 형식의 날짜가 있더라도 항상 new Date(year, month, day, hours, minutes, seconds, milliseconds)
형식을 사용하여 날짜를 생성하는 것이 좋습니다. 팀의 모든 프로그래머가 이 간단한 규칙을 따른다면 Date
생성자를 사용할 때와 마찬가지로 명시적이므로 장기적으로 코드를 유지 관리하는 것이 매우 쉬울 것입니다.
멋진 부분은 마지막 4개의 매개변수가 0인 경우 생략할 수 있는 변형을 사용할 수 있다는 것입니다. 즉, new Date(2012, 10, 12)
는 지정되지 않은 매개변수의 기본값이 0이기 때문에 new Date(2012, 10, 12, 0, 0, 0, 0)
와 동일합니다.
예를 들어 날짜 2012-10-12 및 시간 12:30을 제공하는 날짜 및 시간 선택기를 사용하는 경우 다음과 같이 부분을 추출하고 새 Date 객체를 생성할 수 있습니다.
const dateFromPicker = "2012-10-12"; const timeFromPicker = "12:30"; const dateParts = dateFromPicker.split("-"); const timeParts = timeFromPicker.split(":"); const localDate = new Date(dateParts[0], dateParts[1]-1, dateParts[2], timeParts[0], timeParts[1]);
ISO 날짜 형식이 아닌 한 문자열에서 날짜를 생성하지 마십시오. 대신 Date(년, 월, 날짜, 시, 분, 초, 마이크로초) 방법을 사용하십시오.
날짜만 가져오기
예를 들어 사용자의 생년월일과 같은 날짜만 가져오는 경우 형식을 유효한 ISO 날짜 형식으로 변환하여 UTC로 변환할 때 날짜가 앞뒤로 이동할 수 있는 표준 시간대 정보를 제거하는 것이 가장 좋습니다. 예를 들어:
const dateFromPicker = "12/20/2012"; const dateParts = dateFromPicker.split("/"); const ISODate = dateParts[2] + "-" + dateParts[0] + "-" + dateParts[1]; const birthDate = new Date(ISODate).toISOString();
잊어버린 경우 유효한 ISO 날짜 형식(YYYY-MM-DD)의 입력으로 Date
개체를 생성하면 브라우저의 표준 시간대가 기본값이 아닌 UTC가 기본값이 됩니다.
날짜 저장
DateTime은 항상 UTC로 저장하십시오. 항상 ISO 날짜 문자열이나 타임스탬프를 백엔드로 보내십시오.
수 세대에 걸친 컴퓨터 프로그래머는 사용자에게 정확한 현지 시간을 보여주려는 쓰라린 경험 후에 이 단순한 진리를 깨달았습니다. 백엔드에 현지 시간을 저장하는 것은 나쁜 생각입니다. 브라우저가 프론트엔드에서 현지 시간으로의 변환을 처리하도록 하는 것이 좋습니다.
또한 "July 20, 1989 12:10 PM"과 같은 DateTime 문자열을 백엔드로 보내서는 안 된다는 것이 분명해야 합니다. 시간대도 같이 보내더라도 다른 프로그래머가 의도를 이해하고 날짜를 올바르게 구문 분석하여 저장하는 노력이 증가하고 있습니다.
Date 객체의 toISOString()
또는 toJSON()
메서드를 사용하여 로컬 DateTime을 UTC로 변환합니다.
const dateFromUI = "12-13-2012"; const timeFromUI = "10:20"; const dateParts = dateFromUI.split("-"); const timeParts = timeFromUI.split(":"); const date = new Date(dateParts[2], dateParts[0]-1, dateParts[1], timeParts[0], timeParts[1]); const dateISO = date.toISOString(); $.post("http://example.com/", {date: dateISO}, ...)
날짜 및 시간 표시
- REST API에서 타임스탬프 또는 ISO 형식의 날짜를 가져옵니다.
-
Date
개체를 만듭니다. -
toLocaleString()
또는toLocaleDateString()
및toLocaleTimeString()
메서드 또는 날짜 라이브러리를 사용하여 현지 시간을 표시합니다.
const dateFromAPI = "2016-01-02T12:30:00Z"; const localDate = new Date(dateFromAPI); const localDateString = localDate.toLocaleDateString(undefined, { day: 'numeric', month: 'short', year: 'numeric', }); const localTimeString = localDate.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit', second: '2-digit', });
현지 시간도 언제 저장해야 합니까?
“때로는 이벤트가 발생한 시간대를 아는 것이 중요하며 단일 시간대로 변환하면 해당 정보를 취소할 수 없습니다.
"마케팅 프로모션을 하고 있고 점심 시간에 어떤 고객이 주문했는지 알고 싶다면 GMT 정오에 주문한 것처럼 보이는 주문이 실제로 뉴욕에서 아침 식사를 할 때 도움이 되지 않습니다."
이런 상황이 발생하면 현지 시간도 절약하는 것이 현명할 것입니다. 평소와 같이 ISO 형식으로 날짜를 만들고 싶지만 먼저 시간대 오프셋을 찾아야 합니다.
Date 객체의 getTimeZoneOffset()
함수는 주어진 현지 시간에 추가할 때 동등한 UTC 시간을 제공하는 분 수를 알려줍니다. (+-)hh:mm 형식으로 변환하는 것이 좋습니다. 시간대 오프셋임을 더 명확하게 보여주기 때문입니다.
const now = new Date(); const tz = now.gettime zoneOffset();
내 시간대 +05:45의 경우 -345를 얻었습니다. 이것은 반대 기호일 뿐만 아니라 -345와 같은 숫자는 백엔드 개발자에게 완전히 당혹스러울 수 있습니다. 그래서 우리는 이것을 +05:45로 변환합니다.
const sign = tz > 0 ? "-" : "+"; const hours = pad(Math.floor(Math.abs(tz)/60)); const minutes = pad(Math.abs(tz)%60); const tzOffset = sign + hours + ":" + minutes;
이제 나머지 값을 가져오고 로컬 DateTime을 나타내는 유효한 ISO 문자열을 만듭니다.
const localDateTime = now.getFullYear() + "-" + pad(now.getMonth()+1) + "-" + pad(now.getDate()) + "T" + pad(now.getHours()) + ":" + pad(now.getMinutes()) + ":" + pad(now.getSeconds());
원하는 경우 개체에서 UTC 및 현지 날짜를 래핑할 수 있습니다.
const eventDate = { utc: now.toISOString(), local: localDateTime, tzOffset: tzOffset, }
이제 백엔드에서 현지 시간으로 정오 이전에 이벤트가 발생했는지 확인하려면 날짜를 구문 분석하고 getHours()
함수를 사용하면 됩니다.
const localDateString = eventDate.local; const localDate = new Date(localDateString); if(localDate.getHours() < 12) { console.log("Event happened before noon local time"); }
여기서는 tzOffset
을 사용하지 않았지만 향후 디버깅 목적으로 필요할 수 있으므로 여전히 저장합니다. 실제로 시간대 오프셋과 UTC 시간만 보낼 수 있습니다. 그러나 나는 현지 시간도 저장하는 것을 좋아합니다. 왜냐하면 결국 날짜를 데이터베이스에 저장해야 하고 현지 시간을 별도로 저장하면 현지 날짜를 얻기 위해 계산을 수행할 필요 없이 필드를 기반으로 직접 쿼리할 수 있기 때문입니다.
때로는 현지 시간대가 저장되어 있어도 특정 시간대의 날짜를 표시해야 하는 경우가 있습니다. 예를 들어, 이벤트 시간은 가상인 경우 현재 사용자의 시간대를 기준으로 하고 그렇지 않은 경우 물리적으로 발생할 시간대를 기준으로 하는 것이 더 합리적일 수 있습니다. 어쨌든 명시적인 표준 시간대 이름으로 서식을 지정하기 위한 기존 솔루션을 미리 살펴보는 것이 좋습니다.
서버 및 데이터베이스 구성
항상 UTC 시간대를 사용하도록 서버와 데이터베이스를 구성하십시오. (UTC와 GMT는 같은 것이 아닙니다. 예를 들어 GMT는 여름에 BST로의 전환을 의미할 수 있지만 UTC는 절대 그렇지 않습니다.)
우리는 특히 의도하지 않은 경우 시간대 변환이 얼마나 고통스러운지 이미 보았습니다. 항상 UTC DateTime을 보내고 서버를 UTC 표준 시간대에 맞추도록 구성하면 삶이 더 쉬워질 수 있습니다. 시간대 변환을 수행할 필요가 없으므로 백엔드 코드가 훨씬 간단하고 깨끗해집니다. 전 세계 서버에서 들어오는 DateTime 데이터를 손쉽게 비교하고 정렬할 수 있습니다.
Code in the back end should be able to assume the time zone of the server to be UTC (but should still have a check in place to be sure). A simple configuration check saves having to think about and code for conversions every time new DateTime code is written.
It's Time for Better Date Handling
Date manipulation is a hard problem. The concepts behind the practical examples in this article apply beyond JavaScript, and are just the beginning when it comes to properly handling DateTime data and calculations. Plus, every helper library will come with its own set of nuances—which is even true of the eventual official standard support{target=”_blank”} for these types of operations.
The bottom line is: Use ISO on the back end, and leave the front end to format things properly for the user. Professional programmers will be aware of some of the nuances, and will (all the more decidedly) use well-supported DateTime libraries on both the back end and the front end. Built-in functions on the database side are another story, but hopefully this article gives enough background to make wiser decisions in that context, too.