Skip to content

Commit 6a85508

Browse files
committed
日付の他の言語とのやりとりについて詳しく追記
1 parent 1045c02 commit 6a85508

File tree

1 file changed

+142
-4
lines changed

1 file changed

+142
-4
lines changed

otherbuiltinobjects.rst

Lines changed: 142 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -258,16 +258,36 @@ TypeScriptの\ ``Date``\ 型は数字に毛の生えたようなものですの
258258
* サーバーでは常にエポック時刻で扱う(ただし、言語によっては秒単位だったり、ミリ秒単位だったり、マイクロ秒単位だったり違いはあるため、そこはルールを決めておきましょう)
259259
* サーバーからフロントに送った段階で\ ``new Date()``\ などを使って、ローカル時刻化する
260260

261-
日付のフォーマット
261+
特定の日時の\ ``Date``\ インスタンスの作成
262+
-----------------------------------------------------
263+
264+
特定の日時インスタンスを作成するには、\ ``Date()``\ コンストラクタの引数に数値を設定して作成します。月の数値が1少なく評価される(1月は0)な点に注意が必要です。この日時は現在のタイムゾーンで評価されます。
265+
266+
.. code-block:: ts
267+
268+
// 2020年9月21日21時10分5秒
269+
// 日本で実行すると日本時間21時(UTCでは9時間前の12時)に
270+
const d = new Date(2020, 8, 21, 21, 10, 5)
271+
272+
UTCの時刻から生成したい場合には、\ ``Date.UTC()``\ 関数を使います。これはエポック秒を返すのでこれを\ ``new Date()``\ に渡すことで、UTC指定の時刻のインスタンスが作成できます。
273+
274+
// UTCの2020年9月21日11時10分5秒
275+
// 日本で実行すると日本時間20時(日本時間はUTCは9時間進んでいるように見える)に
276+
const d = new Date(Date.UTC(2020, 8, 21, 11, 10, 5))
277+
278+
日付のフォーマット出力
262279
-------------------------------------------
263280

264-
RFC-3393形式にするには\ ``toISOString()``\ メソッドを使います。
281+
:RFC:`3393`\ 形式にするには\ ``toISOString()``\ メソッドを使います\ ``toString()``\ だとECMAScriptの仕様書で定められたロケール情報も含む文字列で出力を行ます。後者の場合、ミリ秒単位のデータは丸められてしまいます
265282

266283
.. code-block:: ts
267284
268285
const now = new Date()
269-
const.toISOString()
270-
// '2020-09-06T13:34:37.557Z'
286+
now.toISOString()
287+
// '2020-09-21T12:38:15.655Z'
288+
289+
now.toString()
290+
// 'Mon Sep 21 2020 21:38:15 GMT+0900 (Japan Standard Time)'
271291
272292
短い形式やオリジナルの形式にするには自分でコードを書く必要があります。短く日時を表現しようとする場合のコードは次のようになります。月のみカレンダーの表記と異なって、0が1月になる点に注意してください。
273293

@@ -289,3 +309,121 @@ RFC-3393形式にするには、\ ``toISOString()``\ メソッドを使います
289309
// "2020/09/06 13:55:43"
290310
291311
``padStart()``\ と、テンプレート文字列のおかげで、以前よりははるかに書きやすくなりましたが、Day.jsなどの提供するフォーマット関数を使った方が短く可読性も高くなるでしょう。
312+
313+
日付データの交換
314+
-------------------
315+
316+
クライアントとサーバーの間ではJSONなどを通じてデータをやりとりします。JSONでデータを転送するときは数値か文字列で表現する必要があります。日付データは既に説明した通りに、ミリ秒か秒のどちらかの数値で交換するのがもっともコード的には少ない配慮で実現できます。しかし、一方で出力された数字の列を見ても、即座に現在の日時を暗算できる人はいないでしょう。可読性という点では文字列を使いたいこともあるでしょう。本節ではデータをやりとりする相手ごとのデータ変換の仕方について詳しく説明していきます。
317+
318+
TypeScript(含むJavaScript同士)
319+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
320+
321+
TypeScript(含むJavaScript)であれば、\ ``toISOString()``\ でも、\ ``toString()``\ でも、どちらの方式で出力した文字列であってもパースできます。\ ``new Date()``\ に渡すと\ ``Date``\ のインスタンスが、\ ``Date.parse()``\ に渡すと、エポック時刻が帰ってきます。
322+
323+
.. code-block:: ts
324+
325+
const fromToISOString = new Date(`2020-09-21T12:38:15.655Z`)
326+
// 2020-09-21T12:38:15.655Z
327+
328+
const fromToString = new Date(`Mon Sep 21 2020 21:38:15 GMT+0900 (Japan Standard Time)`)
329+
// 2020-09-21T12:38:15.000Z
330+
331+
const fromToISOStringEpoch = Date.parse(`2020-09-21T12:38:15.655Z`)
332+
// 1600691895655
333+
334+
const fromToStringEpoch = Date.parse(`Mon Sep 21 2020 21:38:15 GMT+0900 (Japan Standard Time)`)
335+
// 1600691895000
336+
337+
Goとの交換の場合
338+
~~~~~~~~~~~~~~~~~~~~~~~~
339+
340+
Goでは日付のフォーマットが何種類か選べますが、このうち、タイムゾーンの時差が数値で入っているフォーマットはTypeScriptでパース可能です。ナノ秒の情報がミリ秒に丸められてしまいますが、一番精度良く伝達できるのは\ ``time.RFC3339Nano``\ の出力です。
341+
342+
* ``time.RubyDate``
343+
* ``time.RFC822Z``
344+
* ``time.RFC1123Z``
345+
* ``time.RFC3339``
346+
* ``time.RFC3339Nano``
347+
348+
.. code-block:: go
349+
:caption: GoでRFC3339Nanoで出力
350+
351+
package main
352+
353+
import (
354+
"fmt"
355+
"time"
356+
)
357+
358+
func main() {
359+
now := time.Now()
360+
fmt.Println(now.Format(time.RFC3339Nano))
361+
// 2020-09-21T21:35:45.057076+09:00
362+
}
363+
364+
.. code-block:: ts
365+
:caption: TypeScriptでパース
366+
367+
const receivedFromGo = new Date(`2020-09-21T21:35:45.057076+09:00`)
368+
// 2020-09-21T12:35:45.057Z
369+
370+
他の言語に出力する場合、\ ``toISOString()``\ が無難でしょう。Goは\ ``time.RFC3339``\ か、\ ``time.RFC3339Nano``\ を使ってパースできます。結果はどちらも同じです。
371+
372+
.. code-block:: go
373+
374+
package main
375+
376+
import (
377+
"fmt"
378+
"time"
379+
)
380+
381+
func main() {
382+
t, err := time.Parse(time.RFC3339, "2020-09-21T12:38:15.655Z")
383+
fmt.Println(t)
384+
// 2020-09-21 12:38:15.655 +0000 UTC
385+
}
386+
387+
Pythonとの交換の場合
388+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
389+
390+
Pythonで出力する場合は\ ``datetime.datetime.isoformat()``\ メソッドを使うと良いでしょう。このメソッドはタイムゾーン情報を取り払い、現在のタイムゾーンの表記そのものをフォーマットして出力します。TypeScriptの\ ``new Date()``\ はUTCであることを前提としてパースするため、出力時はUTCとして出すように心がける必要があります。このUTCで出色された文字列はTypeScriptでパースできます。
391+
392+
.. code-block:: python
393+
394+
from datetime import datetime, timezone
395+
396+
# localの場合はastimezone()を呼んでUTCに
397+
localtime = datetime.now()
398+
utctime = localtime.astimezone(timezone.utc)
399+
utctime.isoformat()
400+
# 2020-09-21T13:42:58.279772+00:00
401+
402+
# あるいは、最初からUTCで扱う
403+
utctime = datetime.utcnow()
404+
utctime.isoformat()
405+
# 2020-09-21T13:42:58.279772+00:00
406+
407+
パースはやっかいです。Stack Overflowでスレッドが立つぐらいのネタです\ [#]_\ 。Python 3.7からは``fromisoformat()``\ というクラスメソッドが増えましたが、以前からの\ ``datetime.strptime()``\ にフォーマット指定を与えた方が高速とのことです。
408+
409+
.. code-block:: python
410+
411+
from datetime import datetime
412+
413+
s = '2020-09-21T12:38:15.655Z'
414+
415+
datetime.fromisoformat(s.replace('Z', '+00:00'))
416+
# datetime.datetime(2020, 9, 21, 12, 38, 15, 655000, tzinfo=datetime.timezone.utc)
417+
418+
datetime.strptime(s, '%Y-%m-%dT%H:%M:%S.%f%z')
419+
# datetime.datetime(2020, 9, 21, 12, 38, 15, 655000, tzinfo=datetime.timezone.utc)
420+
421+
いっそのこと、エポック時刻で扱う方法の方がシンプルでしょう。TypeScriptはミリ秒単位で、Pythonは秒単位なので、1000で割ってから渡す必要があるのと、UTCの数値であることを明示する必要があります。
422+
423+
.. code-block:: python
424+
425+
from datetime import datetime
426+
datetime.fromtimestamp(1600691895655 / 1000.0, timezone.utc)
427+
# datetime.datetime(2020, 9, 21, 12, 38, 15, 655000, tzinfo=datetime.timezone.utc)
428+
429+
.. [#] https://stackoverflow.com/questions/127803/how-do-i-parse-an-iso-8601-formatted-date

0 commit comments

Comments
 (0)