0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Pythonの日時が9時間ずれる5つの落とし穴|utcnow・naiveとawareの比較・replace(tzinfo)・夏時間・tzdataなしのコンテナ

0
Posted at

TL;DR

  • datetime.utcnow().timestamp() は、日本時間(JST)のマシンでは 32,400秒(9時間)過去 の値になります。naive な datetime はローカル時刻として解釈されるためです。
  • naive と aware の datetime は、< で比べると TypeError、== で比べると 例外なしで False になります。
  • replace(tzinfo=...) は時刻を変換せず、ラベルを貼り替えるだけです。UTC の値に JST を貼ると9時間ずれます。
  • 夏時間のある地域では、同じタイムゾーンどうしの + timedelta(days=1) が「壁時計で1日後」になり、実際の経過時間は25時間でした。
  • LLM API のレート制限の解除時刻(Unix 秒)を naive どうしで計算すると、JST のマシンでは「1分後」が541分後になりました。Z 付きの期限から tz を外して datetime.now() と比べると、残り2時間が -7時間(期限切れ)と判定されました。
  • システムの tz データベースも PyPI の tzdata パッケージも無い環境では、ZoneInfo("Asia/Tokyo") が ZoneInfoNotFoundError になります。tzdata を依存に入れれば動き、夏時間の無い JST だけなら固定オフセットでも足ります。
  • Python 3.13.5(Debian 13)で再現と修正を実行し、最後に「平日13:15(JST)の次回実行時刻」を求める関数を、上の落とし穴を避けて組みました。

定時投稿やバッチの予約、API の締切チェックなど、AIエージェントの運用でも日時の計算はあちこちに出てきます。サーバーは UTC、開発機は JST、API のレスポンスは Z 付きの ISO 8601 という組み合わせはよくあり、そこで「9時間ずれる」不具合が起きやすくなります。この記事では、実際に踏みやすい5つを再現し、直し方と実行結果を並べます。

実行環境

$ python3 --version
Python 3.13.5

OS のタイムゾーンによって結果が変わる例があるので、各コマンドの先頭に TZ=Asia/Tokyo または TZ=UTC を付けて実行しています。

全体像:naive と aware

Python の公式ドキュメントは、tzinfo を持たない datetime を naive、持つものを aware と呼び分けています。naive な値は「どのタイムゾーンの時刻か」の情報を持たないので、多くのメソッドがローカル時刻として扱います。以下の落とし穴は、ほとんどがこの区別から来ています。

落とし穴1:utcnow() の timestamp が9時間ずれる

「UTC の現在時刻」のつもりで datetime.utcnow() を使うと、返ってくるのは tzinfo の無い naive な値です。

case1_utcnow.py
import time, warnings
from datetime import datetime, timezone

with warnings.catch_warnings(record=True) as caught:
    warnings.simplefilter("always")       # DeprecationWarning を記録する
    naive = datetime.utcnow()             # tzinfo なしの「UTCのつもり」
aware = datetime.now(timezone.utc)        # tzinfo=UTC 付き
print("警告               :", caught[0].category.__name__)
print("time.time()        :", int(time.time()))
print("utcnow().timestamp :", int(naive.timestamp()))
print("now(utc).timestamp :", int(aware.timestamp()))
print("差(秒)             :", int(aware.timestamp() - naive.timestamp()))
$ TZ=Asia/Tokyo python3 case1_utcnow.py
警告               : DeprecationWarning
time.time()        : 1792976259
utcnow().timestamp : 1792943859
now(utc).timestamp : 1792976259
差(秒)             : 32400

$ TZ=UTC python3 case1_utcnow.py
警告               : DeprecationWarning
time.time()        : 1792976259
utcnow().timestamp : 1792976259
now(utc).timestamp : 1792976259
差(秒)             : 0

JST では差がちょうど32,400秒(9時間×3,600秒)になり、UTC では0でした。ドキュメントにあるとおり、timestamp() は naive な値をローカル時刻とみなして 変換します。UTC の時刻なのに JST として解釈されるので、9時間過去の値になります。サーバー(UTC)では正しく動き、手元(JST)でだけずれるので、テストをすり抜けやすい不具合です。

utcnow() は Python 3.12 で非推奨になり、実行すると DeprecationWarning が出ます(上のスクリプトでは警告を記録して、種類だけを表示しています)。

直し方:現在時刻は最初から aware で作ります。

fix1_aware.py
import time
from datetime import UTC, datetime

aware = datetime.now(UTC)                 # Python 3.11 以降は datetime.UTC が使える
print("time.time()        :", int(time.time()))
print("now(UTC).timestamp :", int(aware.timestamp()))
print("isoformat          :", aware.isoformat(timespec="seconds"))
$ TZ=Asia/Tokyo python3 fix1_aware.py
time.time()        : 1792976259
now(UTC).timestamp : 1792976259
isoformat          : 2026-10-26T00:57:39+00:00

JST のマシンでも time.time() と一致し、isoformat() に +00:00 が付きました。同じ理由で utcfromtimestamp() も非推奨なので、datetime.fromtimestamp(ts, UTC) に置き換えます。

落とし穴2:naive と aware を比べると、< は例外・== は常に False

API から来た締切(aware)と、datetime.now()(naive)を比べるケースです。

case2_compare.py
from datetime import datetime, timezone
deadline = datetime.fromisoformat("2026-10-27T04:15:00+00:00")  # APIから来たaware
now = datetime.now()                                           # naive
try:
    print(now < deadline)
except TypeError as e:
    print("TypeError:", e)
try:
    print(datetime.now() == deadline)
except TypeError as e:
    print("TypeError:", e)
$ TZ=Asia/Tokyo python3 case2_compare.py
TypeError: can't compare offset-naive and offset-aware datetimes
False

大小比較は例外で止まるのでまだ気づけます。厄介なのは == で、例外を出さずに False を返します。ドキュメントにも「naive と aware の大小比較は TypeError」「naive と aware は等しくならない」とあります。重複チェックや「同じ時刻なら処理済み」といった判定に == を使っていると、条件が一度も成り立たないまま静かに動き続けます。

直し方:比較の前に両方を aware にそろえます。

fix2_compare.py
from datetime import UTC, datetime
deadline = datetime.fromisoformat("2026-10-27T04:15:00+00:00")
now = datetime.now(UTC)                                        # aware どうしで比べる
print("now < deadline :", now < deadline)
print("締切まで       :", deadline - now)
print("JSTでの締切    :", deadline.astimezone().isoformat())     # 引数なしはOSのローカルtz
$ TZ=Asia/Tokyo python3 fix2_compare.py
now < deadline : True
締切まで       : 1 day, 3:17:20.124816
JSTでの締切    : 2026-10-27T13:15:00+09:00

aware どうしなら、タイムゾーンが違っても同じ瞬間として比べられ、差も正しく出ます。なお、Python 3.11 から fromisoformat() は末尾の Z も読めるようになりました(What's New In Python 3.11、後述の next_run.py で確認しています)。

実例:LLM API が返す時刻と比べる

LLM の API でも、時刻はよく2つの形で返ってきます。レート制限の解除時刻のような Unix 秒 と、バッチ処理の期限のような Z 付きの ISO 8601 です。どちらも naive な現在時刻と混ぜると、落とし穴1・2がそのまま起きます。ここでは「60秒後に解除」「2時間後が期限」という応答を作って計算しました。

llm_api_times.py
"""LLM API が返す時刻(Unix 秒と Z 付き ISO 8601)を、現在時刻と比べる。"""
import time, warnings
from datetime import UTC, datetime, timedelta
warnings.filterwarnings("ignore", category=DeprecationWarning)  # utcnow() の警告は落とし穴1で確認済み

# レート制限の解除時刻(Unix 秒)。「60秒後に解除」という応答を作る
reset = int(time.time()) + 60
wrong = datetime.fromtimestamp(reset) - datetime.utcnow()        # naive どうし
right = datetime.fromtimestamp(reset, UTC) - datetime.now(UTC)   # aware どうし
print(f"解除まで(naive)       : {wrong.total_seconds() / 60:.1f} 分")
print(f"解除まで(aware)       : {right.total_seconds() / 60:.1f} 分")

# バッチの期限(Z 付き ISO 8601)。「2時間後が期限」という応答を作る
expires_at = (datetime.now(UTC) + timedelta(hours=2)).strftime("%Y-%m-%dT%H:%M:%SZ")
deadline = datetime.fromisoformat(expires_at)                    # Z 付きは aware になる
wrong_left = deadline.replace(tzinfo=None) - datetime.now()     # TypeError を避けて tz を外す
right_left = deadline - datetime.now(UTC)
print(f"期限まで(tzを外す)    : {wrong_left.total_seconds() / 3600:.1f} 時間")
print(f"期限まで(now(UTC))    : {right_left.total_seconds() / 3600:.1f} 時間")
$ TZ=Asia/Tokyo python3 llm_api_times.py
解除まで(naive)       : 541.0 分
解除まで(aware)       : 1.0 分
期限まで(tzを外す)    : -7.0 時間
期限まで(now(UTC))    : 2.0 時間

$ TZ=UTC python3 llm_api_times.py
解除まで(naive)       : 1.0 分
解除まで(aware)       : 1.0 分
期限まで(tzを外す)    : 2.0 時間
期限まで(now(UTC))    : 2.0 時間

JST のマシンでは、naive どうしの計算で解除までが541分(9時間+1分)になりました。fromtimestamp(reset) は JST の時刻を、utcnow() は UTC の時刻を返すので、差に9時間が混ざります。この値で待つと、1分で済むところを9時間以上待つことになります。期限のほうは、TypeError を避けようと replace(tzinfo=None) で tz を外して datetime.now() と比べた結果、残り2時間なのに -7.0 時間(期限切れ)と判定されました。どちらも TZ=UTC では正しい値になるので、UTC のサーバーと JST の手元で結果が変わります。aware どうし(fromtimestamp(ts, UTC) と datetime.now(UTC))で計算すれば、OS のタイムゾーンによらず 1.0 分と 2.0 時間になりました。

落とし穴3:replace(tzinfo=) は変換しない

DB やログから読んだ「UTC のつもりの naive な値」を JST で表示したいとき、replace を使うと9時間ずれます。

case3_replace.py
from datetime import datetime
from zoneinfo import ZoneInfo
tokyo = ZoneInfo("Asia/Tokyo")
utc_naive = datetime(2026, 10, 27, 4, 15)          # DBから来た「UTCのつもり」のnaive値
wrong = utc_naive.replace(tzinfo=tokyo)            # 変換せずにラベルだけ貼る
right = utc_naive.replace(tzinfo=ZoneInfo("UTC")).astimezone(tokyo)
print("replace  :", wrong.isoformat())
print("astimezone:", right.isoformat())
print("ずれ      :", right - wrong)
$ TZ=Asia/Tokyo python3 case3_replace.py
replace  : 2026-10-27T04:15:00+09:00
astimezone: 2026-10-27T13:15:00+09:00
ずれ      : 9:00:00

replace(tzinfo=tokyo) は、時刻の 04:15 はそのままで「これは JST です」というラベルを付けるだけです。その結果、UTC 04:15(JST 13:15)だったはずの値が JST 04:15 になりました。

直し方:replace(tzinfo=) を使ってよいのは、その値が本当はどのタイムゾーンの時刻か を付けるときだけです。上の例なら、まず replace(tzinfo=UTC) で「UTC の時刻」と明示し、そのあと astimezone(tokyo) で JST に変換します。表示用のタイムゾーンへの変換は、必ず astimezone を使います。

落とし穴4:夏時間のある地域では、+ timedelta(days=1) が25時間になる

日本には夏時間がありませんが、海外の利用者向けの予約や、米国の API の締切を扱うと出てきます。米国の夏時間は2026年11月1日に終わります。

case4_dst.py
from datetime import datetime, timedelta, timezone
from zoneinfo import ZoneInfo
ny = ZoneInfo("America/New_York")
start = datetime(2026, 10, 31, 9, 0, tzinfo=ny)    # 米国の夏時間終了は 2026-11-01
nxt = start + timedelta(days=1)
print("start      :", start.isoformat())
print("+1 day     :", nxt.isoformat())
print("+24 hours  :", (start + timedelta(hours=24)).isoformat())
print("実経過時間  :", nxt.astimezone(timezone.utc) - start.astimezone(timezone.utc))
print("同じtzの差  :", nxt - start)
print("違うtzの差  :", nxt - start.astimezone(timezone.utc))
# 存在しない時刻(2026-03-08 02:30 は夏時間開始で飛ばされる)
gap = datetime(2026, 3, 8, 2, 30, tzinfo=ny)
print("存在しない  :", gap.isoformat(), "-> UTC往復:", gap.astimezone(timezone.utc).astimezone(ny).isoformat())
# 2回ある時刻(2026-11-01 01:30)
for fold in (0, 1):
    t = datetime(2026, 11, 1, 1, 30, tzinfo=ny, fold=fold)
    print(f"fold={fold}     :", t.isoformat(), t.tzname())
$ TZ=Asia/Tokyo python3 case4_dst.py
start      : 2026-10-31T09:00:00-04:00
+1 day     : 2026-11-01T09:00:00-05:00
+24 hours  : 2026-11-01T09:00:00-05:00
実経過時間  : 1 day, 1:00:00
同じtzの差  : 1 day, 0:00:00
違うtzの差  : 1 day, 1:00:00
存在しない  : 2026-03-08T02:30:00-05:00 -> UTC往復: 2026-03-08T03:30:00-04:00
fold=0     : 2026-11-01T01:30:00-04:00 EDT
fold=1     : 2026-11-01T01:30:00-05:00 EST

分かったことは3つです。

  1. aware な datetime に timedelta を足すと 壁時計の時刻 で計算され、同じ tzinfo どうしの引き算も壁時計の差になります。+1 day も +24 hours も結果は翌日の 9:00 で、UTC に直すと実際には25時間たっています。同じ tz どうしで引き算すると「1日」と表示され、ずれが見えません。tzinfo が違う値どうし(ここでは片方を UTC に直した値)の引き算は UTC に直して計算されるので、1 day, 1:00:00(25時間)と出ました。
  2. 夏時間の開始で飛ばされる 02:30 のような 存在しない時刻 も、エラーにならずに作れます。UTC を経由して戻すと 03:30 に変わりました。
  3. 夏時間の終了で 2回ある時刻(01:30)は、fold=0 なら先の EDT(-04:00)、fold=1 なら後の EST(-05:00)になります。fold は PEP 495 で入った属性です。

直し方:「毎日 9:00」のような予定は、経過時間ではなく 日付+壁時計の時刻 で組み立てます。逆に「24時間後」のように経過時間が大事なら、UTC で計算してから現地時刻に直します。

wallclock_daily.py
"""夏時間のある地域で「毎日 9:00」を作るなら、+24時間ではなく日付+壁時計で組み立てる。"""
from datetime import datetime, time, timedelta, UTC
from zoneinfo import ZoneInfo
ny = ZoneInfo("America/New_York")
start = datetime(2026, 10, 31, 9, 0, tzinfo=ny)
via_utc = (start.astimezone(UTC) + timedelta(hours=24)).astimezone(ny)   # 経過時間で24h後
via_wall = datetime.combine(start.date() + timedelta(days=1), time(9, 0), tzinfo=ny)
print("UTCで+24h :", via_utc.isoformat())
print("壁時計9:00:", via_wall.isoformat())
$ TZ=Asia/Tokyo python3 wallclock_daily.py
UTCで+24h : 2026-11-01T08:00:00-05:00
壁時計9:00: 2026-11-01T09:00:00-05:00

どちらが正しいかは用途で決まります。「ちょうど24時間後に再試行」なら上、「毎朝9時に送信」なら下です。どちらのつもりで書いたのかを、コード上で区別できるようにしておくのが大事です。

落とし穴5:tz データベースの無いコンテナでは ZoneInfo が失敗する

zoneinfo はタイムゾーンのデータを自分では持たず、OS の tz データベース(/usr/share/zoneinfo など)を探し、見つからなければ PyPI の tzdata パッケージにフォールバックします。そのため、この落とし穴は システムのデータが無く、tzdata パッケージもインストールされていない環境で 起きます。ドキュメントによると、PYTHONTZPATH="" を指定するとシステムのデータを使わない設定になります。ただ、手元の python3 には tzdata が入っていたので、それだけでは再現しませんでした(下の2つ目のコマンド)。そこで、追加のパッケージが見えない venv を --without-pip で作って確かめました。

case5_tzdata.py
import zoneinfo
print("TZPATH:", zoneinfo.TZPATH)
try:
    print(zoneinfo.ZoneInfo("Asia/Tokyo"))
except zoneinfo.ZoneInfoNotFoundError as e:
    print("ZoneInfoNotFoundError:", e)
$ python3 case5_tzdata.py
TZPATH: ('/usr/share/zoneinfo', '/usr/lib/zoneinfo', '/usr/share/lib/zoneinfo', '/etc/zoneinfo')
Asia/Tokyo

$ PYTHONTZPATH="" python3 case5_tzdata.py
TZPATH: ()
Asia/Tokyo

$ python3 -c 'import importlib.metadata as m; print(m.version("tzdata"))'
2026.5

$ python3 -m venv --without-pip clean && PYTHONTZPATH="" clean/bin/python case5_tzdata.py
TZPATH: ()
ZoneInfoNotFoundError: 'No time zone found with key Asia/Tokyo'

$ python3 -m venv .venv && .venv/bin/pip install -q tzdata

$ .venv/bin/python -c 'import importlib.metadata as m; print(m.version("tzdata"))'
2026.5

$ PYTHONTZPATH="" .venv/bin/python case5_tzdata.py
TZPATH: ()
Asia/Tokyo

システムのデータも tzdata も無い venv では ZoneInfoNotFoundError になり、venv に tzdata を入れると、同じ条件でも Asia/Tokyo を読めました。ドキュメントは、システムにデータが無ければ tzdata にフォールバックすること、クロスプラットフォームで動かすなら tzdata を依存に宣言することを勧めています。手元では動くのに、軽量なコンテナイメージにしたとたん起動時に落ちる、という形で出やすい不具合です。

直し方:requirements.txt や pyproject.toml の依存に tzdata を入れておきます。コンテナなら、OS 側に tz データを入れる方法もあります。Debian / Ubuntu 系のイメージでは、apt-get install -y tzdata で入る tzdata パッケージが /usr/share/zoneinfo を提供しています(手元の Debian 13 で確認)。

$ dpkg -S /usr/share/zoneinfo/Asia/Tokyo
tzdata: /usr/share/zoneinfo/Asia/Tokyo

夏時間の無い JST だけを扱うなら、timezone(timedelta(hours=9)) の固定オフセットでも足ります。tz データを使わないので、上で ZoneInfoNotFoundError が出た venv でも動きました。夏時間のある地域には使えないので、そこでは ZoneInfo と tzdata を使います。

fixed_offset.py
"""夏時間の無い JST だけなら、固定オフセットで tz データなしに扱える。"""
from datetime import datetime, timedelta, timezone
JST = timezone(timedelta(hours=9), "JST")
t = datetime(2026, 10, 27, 4, 15, tzinfo=timezone.utc).astimezone(JST)
print(t.isoformat(), t.tzname())
$ PYTHONTZPATH="" clean/bin/python fixed_offset.py
2026-10-27T13:15:00+09:00 JST

仕上げ:平日13:15(JST)の次回実行時刻を求める

ここまでの対策をまとめて、定時ジョブの「次回実行時刻」を求める関数を書きました。AIエージェントを毎営業日の決まった時刻に動かす、といった予約実行で使う計算です。

  • naive な値は受け付けない(落とし穴1・2)
  • 比較の前に astimezone で現地時刻に変換する(落とし穴3)
  • 候補は datetime.combine で日付+壁時計の時刻から作る(落とし穴4)
  • 祝日は対象外(必要なら祝日リストで除外する)
next_run.py
"""平日 13:15(Asia/Tokyo)の次回実行時刻を、awareなdatetimeだけで求める。

祝日は対象外(必要なら祝日リストで除外する)。
"""
from datetime import UTC, datetime, time, timedelta
from zoneinfo import ZoneInfo

TOKYO = ZoneInfo("Asia/Tokyo")
RUN_AT = time(13, 15)

def next_run(now: datetime) -> datetime:
    if now.tzinfo is None:
        raise ValueError("naive datetime は受け付けない(tzinfo を付けて渡す)")
    local = now.astimezone(TOKYO)               # 比較の前に必ず現地時刻へ変換
    day = local.date()
    while True:
        cand = datetime.combine(day, RUN_AT, tzinfo=TOKYO)  # 壁時計の時刻で組み立てる
        if cand.weekday() < 5 and cand > local:
            return cand
        day += timedelta(days=1)

if __name__ == "__main__":
    cases = [
        "2026-10-23T04:00:00+00:00",  # 金 13:00 JST → 当日 13:15
        "2026-10-23T04:15:00+00:00",  # 金 13:15 JST ちょうど → 翌営業日
        "2026-10-24T01:00:00+00:00",  # 土 → 月曜
        "2026-10-26T15:30:00+00:00",  # 月 UTC 15:30 = 火 0:30 JST → 火 13:15
    ]
    for s in cases:
        now = datetime.fromisoformat(s)
        r = next_run(now)
        print(f"{s} -> {r.isoformat()} ({r:%a}) = {r.astimezone(UTC):%Y-%m-%dT%H:%MZ}")
    print("Z 付きも読める:", datetime.fromisoformat("2026-10-27T04:15:00Z").isoformat())
    try:
        next_run(datetime(2026, 10, 23, 13, 0))
    except ValueError as e:
        print("ValueError:", e)

OS のタイムゾーンを JST と UTC に変えて、同じ結果になるかを確かめました。

$ TZ=Asia/Tokyo python3 next_run.py
2026-10-23T04:00:00+00:00 -> 2026-10-23T13:15:00+09:00 (Fri) = 2026-10-23T04:15Z
2026-10-23T04:15:00+00:00 -> 2026-10-26T13:15:00+09:00 (Mon) = 2026-10-26T04:15Z
2026-10-24T01:00:00+00:00 -> 2026-10-26T13:15:00+09:00 (Mon) = 2026-10-26T04:15Z
2026-10-26T15:30:00+00:00 -> 2026-10-27T13:15:00+09:00 (Tue) = 2026-10-27T04:15Z
Z 付きも読める: 2026-10-27T04:15:00+00:00
ValueError: naive datetime は受け付けない(tzinfo を付けて渡す)

$ TZ=UTC python3 next_run.py
2026-10-23T04:00:00+00:00 -> 2026-10-23T13:15:00+09:00 (Fri) = 2026-10-23T04:15Z
2026-10-23T04:15:00+00:00 -> 2026-10-26T13:15:00+09:00 (Mon) = 2026-10-26T04:15Z
2026-10-24T01:00:00+00:00 -> 2026-10-26T13:15:00+09:00 (Mon) = 2026-10-26T04:15Z
2026-10-26T15:30:00+00:00 -> 2026-10-27T13:15:00+09:00 (Tue) = 2026-10-27T04:15Z
Z 付きも読める: 2026-10-27T04:15:00+00:00
ValueError: naive datetime は受け付けない(tzinfo を付けて渡す)

4つのケースはどちらの OS 設定でも同じ結果になりました。UTC では月曜の15:30でも、JST では火曜の0:30なので、次回は火曜の13:15になります。曜日の判定を UTC の日付でしていると、ここで1日ずれます。

次回実行時刻や API の期限をログに残すときも、Z か +00:00 の付いた UTC の aware な値で書いておくと、JST の記録や API の応答とあとで並べて比べられます。実行ログを1行1件の JSONL で残す方法は、実行ログを JSONL に残すラッパーの記事にまとめています。

まとめ

落とし穴 起きること 直し方
utcnow().timestamp() JST のマシンで9時間過去になる datetime.now(UTC)
naive と aware の比較 < は TypeError、== は常に False 両方 aware にそろえる
replace(tzinfo=tokyo) 時刻が変換されずにずれる astimezone(tokyo)
夏時間をまたぐ + timedelta 壁時計で計算され、実経過は25時間 用途で「UTCで加算」か「日付+壁時計」を選ぶ
tz データも tzdata も無い環境 ZoneInfoNotFoundError 依存に tzdata(コンテナなら OS の tzdata も可)。JST だけなら固定オフセット
  • 現在時刻は datetime.now(UTC) で作り、naive な値はプログラムの入口で拒否する。
  • API の Unix 秒は fromtimestamp(ts, UTC)、Z 付きの時刻は fromisoformat() で aware のまま読み、datetime.now(UTC) と比べる。
  • タイムゾーンの変換は astimezone。replace(tzinfo=) は「本来の tz を付ける」ときだけ使う。
  • 予定時刻は日付+壁時計の時刻から組み立て、テストは OS の TZ を変えて2回流す。

出典

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?