👋

PrismaでUTC以外のTimezoneを扱いたい

2022/06/24に公開3

GraphQLを使う際にNode.jsのORM、Prismaで日本時刻で保存したかったのですがprismaではUTC以外対応していないようでした。
githubに解決策を掲載してくださっていた方がいましたので、備忘録のためにもまとめます。

https://github.com/prisma/prisma/issues/5051

掲載されていたコード

Create or update with custom now() method
(カスタム now() メソッドで作成または更新する)
// UTC-9 using dayjs
export const dbNow = (): Date => dayjs().add(9, 'hour').toDate()

const now = dbNow()

await prisma.user.create({
  data: {
    createdAt: now,
    updatedAt: now,
  },
})
Use a middleware that modifies all the times from Prisma's result object
(Prismaの結果オブジェクトからすべての時刻を修正するミドルウェアを使用する)
// Subtract 9 hours from all the Date objects recursively
function subtract9Hours(obj: Record<string, unknown>) {
  if (!obj) return

  for (const key of Object.keys(obj)) {
    const val = obj[key]

    if (val instanceof Date) {
      obj[key] = dayjs(val).subtract(9, 'hour').toDate()
    } else if (!isPrimitive(val)) {
      subtract9Hours(val as any)
    }
  }
}

function prismaTimeMod<T>(value: T): T {
  if (value instanceof Date) {
    return dayjs(value).subtract(9, 'hour').toDate() as any
  }

  if (isPrimitive(value)) {
    return value
  }

  subtract9Hours(value as any)

  return value
}

// Create a prisma client instance with timemod
const prisma = new PrismaClient()

prisma.$use(async (params, next) => {
    const result = await next(params)

    return prismaTimeMod(result)
})

※コードに載せている日本語はあとから追加しているので、もともとはなかったです。

2つめのコードのミドルウェアはなぜ必要か

保存する際に、1つめのコードで+9時間するのでDBには日本時間で保存されます。

DBに保存されているcreatedAtの日付が2022-02-01 12:00:00の場合で
findFirst()で値を問い合わせた際に、createdAtの時刻は2022-02-01T12:00:00Z
になり、時間がISO8601でフォーマットされZが値に付きUTC時刻になってしまうので、
矛盾がおきます。UTCになるのであれば、2022-02-01T03:00:00Zになっていないといけないので、これを解消するためにミドルウェアを使う必要があります。

Discussion

rikusen0335rikusen0335

すごく参考になりました!
なんですが、いまのPrisma(v4)でこれをやると作成したモデルにたいして取得時に-9時間された結果が取得される可能性があるため、気をつけた方がいいです

v4になってからかは詳しく見たわけではありませんが、自分の環境だとそうなったことだけ同じようになった方用に書き留めておきます。

rikusen0335rikusen0335

保存/取得方法ですが、個人的には

  1. 保存時に時間系のプロパティのタイムゾーンをUTC(Z+00:00)に変えておく
  2. prismaで追加/更新
  3. 取得時にdayjs、date-fnsなどで日本時間として表示

が望ましいかなと思います。

tekihei2317tekihei2317

手元で確認したところ、@default(now())@updatedAtなどのPrismaが自動的に挿入するところは、MySQLのタイムゾーンによらずUTCになってしまう感じでした。そのため、取得するときに-9時間されてしまいます。JSTで保存するためには、代わりに@default(dbgenerated("NOW(3)"))などを使う必要がありました。

Prismaの対応状況を考えると、強い理由(既存のデータがJSTで保存されているなど)がなければUTCで保存するのが無難かもしれません><

https://zenn.dev/gibjapan/articles/04dd5afde1ca79