Vercel環境でタイムゾーンがズレる場合の対処法

2022-06-08

JavaScriptのDateオブジェクトのインスタンスはUTC(協定世界時)1970年1月0時0分0秒を基準とした相対的なミリ秒として保持されています。

つまり、Dateオブジェクト自体にはタイムゾーンの情報を持っておらず、

同じタイミングで作成したDateオブジェクトのインスタンスでも、

実行環境によっては違った時間になってしまう場合があります。

例えば、Next.jsのSSRの処理内でDateオブジェクトを利用する場合、

ローカルで開発している時のSSRの処理はAsia/Tokyoのタイムゾーンで処理され、

VercelにデプロイしたソースコードのSSRの処理はUTCのタイムゾーンで処理されることになります。

ここで、Dateオブジェクトはミリ秒の情報しか保持しておらず、同じタイミングでDateオブジェクトのインスタンスを作成した場合、

Vercel環境の処理(ここではSSRやnext build時に実行される処理)とブラウザで行われる処理の間で9時間の差が発生します。

Next.jsではSSRで取得したデータを利用してページを表示しますから、ブラウザで表示させると、9時間ずれた状態で表示されてしまいます。

アプリケーションを作成する際には、この点を考慮する必要があります。

対処方法

対応方法としては、Vercelの環境で実行される処理内で発生する9時間のズレを解消すれば良いことになります。

Vercelの実行環境はUTCですから、Vercel実行環境の処理内で作成されるUTCDateオブジェクトを、

Asia/Tokyoというタイムゾーンの時間に合わせれば良いです。

具体的な方法の例として、date-fns-tzというライブラリのutcToZonedTimeという関数を使う方法があります。

この関数を利用すると特定のUTC時刻から任意のタイムゾーンの現地時間としてフォーマットされるDateオブジェクトを返してくれます。

typescript
1const date = utcToZonedTime(new Date(), 'Asia/Tokyo');

Vercel実行環境で処理される可能性のある部分でDateオブジェクトを使う場合に、

必ずutcToZonedTimeで変換するようにしておけば、Vercel環境でのタイムゾーンによるズレは解消されます。

ソースコード例

typescript
1export async function getPublishedNewsOrderBy(): Promise<NewsList> {
2 const q = await query(
3 collection(db, path).withConverter(newsConverter),
4 where('published', '==', true),
5 where(
6 'publishDate',
7 '<=',
8 startOfDay(utcToZonedTime(new Date(), SystemConst.TIMEZONE))
9 ),
10 orderBy('publishDate', 'desc')
11 );
12 const snapshot = await getDocs(q);
13 return snapshot.docs.map((doc) => doc.data());
14}
typescript
1import {
2 DocumentData,
3 FirestoreDataConverter,
4 QueryDocumentSnapshot,
5 SnapshotOptions,
6 WithFieldValue,
7} from '@firebase/firestore';
8import { format } from 'date-fns';
9import { utcToZonedTime } from 'date-fns-tz';
10import { SystemConst } from '@/config/const';
11import { News } from '@/service/model/news';
12
13export const newsConverter: FirestoreDataConverter<News> = {
14 toFirestore(news: WithFieldValue<News>): DocumentData {
15 return {
16 newsTitle: news.newsTitle,
17 publishDate: news.publishDate,
18 };
19 },
20 fromFirestore(
21 snapshot: QueryDocumentSnapshot<DocumentData>,
22 options?: SnapshotOptions
23 ): News {
24 const data = snapshot.data(options);
25 return {
26 id: snapshot.id,
27 publishDate: format(
28 utcToZonedTime(data.publishDate.toDate(), SystemConst.TIMEZONE),
29 'yyyy/M/d'
30 ),
31 };
32 },
33};

Tech Blog

avatar

ソフトウェアエンジニア。1989年生まれ大阪府岸和田市在住のフリーランス。PHP、バックエンド開発が得意。テニス、フットサル、だんじり、ケツメイシ、競馬、プログラミングが好き!最近はWebフロントエンド沼にハマってます!

Copyright © 2022. Junpeko5's Tech Blog