Laravel에서 Asia/Seoul 기준 자정인 Carbon 객체를 UTC 기준으로 변환하기 cover image

Laravel에서 Asia/Seoul 기준 자정인 Carbon 객체를 UTC 기준으로 변환하기

김재동 • July 23, 2019

laravel

인디스쿨의 교사인증 시스템에는 인증이 만료되는 시점을 계산하는 로직이 있는데 원래 코드는 Carbon::now()->startOfDay()->addYear(3)과 같이 인증 받는 시점 기준으로 n년 후의 자정으로 설정이 된다. As-is 시스템에서는 timezone 설정을 모두 Asia/Seoul로 했기 때문에 문제가 없었다.

그런데 얼마전 timezone 때문에 골치아팠던 경험이 있어서 합정 프로젝트는 마음 편하게 서버, DB, Laravel의 모든 timezone을 UTC로 통일하고 있다. 그러다보니 인증 만료 시점이 한국 기준으로 자정이 되는 게 아니라 DB에 00:00:00으로 저장이 되어서 한국 기준으로 오전 9시가 되는 문제가 발생했다.

한국 기준으로 자정이 되려면 DB에는 9시간 전인 전날 날짜의 15:00:00이 되어야 한다. 정말 다양한 방법으로 Carbon 객체를 만들고 저장을 했는데 계속 실패했다.

>>> Carbon\Carbon::now()->timezone('Asia/Seoul')->startOfDay()->addYear(3);
=> Carbon\Carbon @1658502000 {#3439
     date: 2022-07-23 00:00:00.0 Asia/Seoul (+09:00),
   }

먼저 이렇게 하면 Asia/Seoul 기준으로 00:00:00이니까 DB에 저장할 때는 당연히 전날 날짜의 15:00:00으로 저장이 될거라 생각했는데 모델 인스턴스에 이렇게 timezone이 적용된 Carbon 객체를 지정했어도 실제로 저장을 할 때는 Carbon 객체를 toDateString()으로 그냥 변환한 후 저장하는 것 같았다. 즉 설정된 timezone에 상관없이 그냥 시간이 그대로 저장된다는 의미이다.

그래서 계속 삽질을 하다가 아래와 같이 정답을 찾을 수 있었다.

>>> today('Asia/Seoul')->timezone('UTC')->addYear(3);
=> Illuminate\Support\Carbon @1658502000 {#3441
     date: 2022-07-22 15:00:00.0 UTC (+00:00),
   }

참고로 Laravel에는 today()라는 helper가 있는데 오늘의 자정 시각으로 Carbon 객체를 만들어 준다. Carbon::now()->startOfDay()와 같은 의미이다. 그런데 today() 사이에 아래와 같이 timezone을 설정할 수 있다.

>>> today('Asia/Seoul')
=> Illuminate\Support\Carbon @1563807600 {#3439
     date: 2019-07-23 00:00:00.0 Asia/Seoul (+09:00),
   }

이게 결국 위에서 실패한 코드와 같은 의미인데, 실패했던 코드 제일 마지막에 다시 UTC로 timezone을 바꿔도 같은 결과를 얻을 수 있다.

Carbon\Carbon::now()->timezone('Asia/Seoul')->startOfDay()->addYear(3)->timezone('UTC')
=> Carbon\Carbon @1658502000 {#3443
     date: 2022-07-22 15:00:00.0 UTC (+00:00),
   }

timezone 대신 줄여서 아래와 같이 tz로 나타낼 수도 있다.

>>> today('Asia/Seoul')->tz('UTC')->addYear(3);
=> Illuminate\Support\Carbon @1658502000 {#3441
     date: 2022-07-22 15:00:00.0 UTC (+00:00),
   }