SafariでAngularのDatePipe使えない問題
Angularにおいて、ISO8601の文字列を表示する場合を考えます。
@Component({
template:`<p>{{post.created_at | Date:'short'}}`
})
export class DemoComponent{
public post = {
title: 'タイトル',
created_at: '2017-01-03 23:15:27'
}
}
DatePipeは文字列もパースできるので、このようにすれば動くように思います。実際、ChromeやFirefoxではなんの問題もなく動きます。
しかし、Safariで実行すると
[Error] EXCEPTION: Error in ./TestComponent class TestComponent - inline template:30:38 caused by: Invalid argument '2017-01-25 21:00:10' for pipe 'DatePipe'
handleError (main.bundle.js:61490)
next (main.bundle.js:43128:94)
(anonymous function) (main.bundle.js:45175)
__tryOrUnsub (main.bundle.js:4737)
next (main.bundle.js:4686)
_next (main.bundle.js:4639)
next (main.bundle.js:4603)
next (main.bundle.js:10467)
emit (main.bundle.js:45161)
triggerError (main.bundle.js:32869)
onHandleError (main.bundle.js:32830)
runTask (main.bundle.js:128472)
drainMicroTaskQueue (main.bundle.js:128723)
invoke (main.bundle.js:128654)
[Error] ORIGINAL EXCEPTION: Invalid argument '2017-01-25 21:00:10' for pipe 'DatePipe'
handleError (main.bundle.js:61492)
next (main.bundle.js:43128:94)
(anonymous function) (main.bundle.js:45175)
__tryOrUnsub (main.bundle.js:4737)
next (main.bundle.js:4686)
_next (main.bundle.js:4639)
next (main.bundle.js:4603)
next (main.bundle.js:10467)
emit (main.bundle.js:45161)
triggerError (main.bundle.js:32869)
onHandleError (main.bundle.js:32830)
runTask (main.bundle.js:128472)
drainMicroTaskQueue (main.bundle.js:128723)
invoke (main.bundle.js:128654)
のようなエラーが発生します。
DatePipeは渡された形式に応じてDateオブジェクトを作成しようとするのですが、Safariだとパースに失敗します。そのため、exceptionが投げられるようになっています。
dateParseパイプを作って解決する
macOSのSafariだけならシカトしても良い(?)のですが、iOSでも同じ問題が発生するため、放置することはなかなか難しいです。そこで、Date
に渡す前にdateParse
という独自のパイプを噛ますことで解決しました。
こんなパイプを定義します。
import { Pipe, PipeTransform } from '@angular/core';
export function parseIsoDatetime(dtstr: string): Date {
const dt = dtstr.split(/[: T-]/).map(parseFloat);
return new Date(dt[0], dt[1] - 1, dt[2], dt[3] || 0, dt[4] || 0, dt[5] || 0, 0);
}
@Pipe({
name: 'dateParse'
})
export class DateParsePipe implements PipeTransform {
transform(value: any | Date): Date {
if (value instanceof Date) {
// Dateはそのまま返す
return value;
}
if (typeof value === 'number') {
return new Date(value);
}
if (typeof value === 'string') {
const parsed = Date.parse(value);
if (Number.isNaN(parsed)) {
const challenge = parseIsoDatetime(value);
if (Number.isNaN(challenge.getTime())) {
return null;
} else {
return challenge;
}
}
return new Date(value);
}
return null;
}
}
冒頭のparseIsoDatetime
で文字列をパースしています。自分の環境ではタイムゾーンを扱うことがなかったので一番シンプルなこの関数を使用していますが、それが必要な場合でもこの部分を置き換えれば動くはずです。Moment.jsを入れても良いと思います。
後は、ngModulesから読み込み、テンプレートを
<p>{{post.created_at | dateParse | Date:'short'}}</html>
のように書き換えてあげれば、Safariでも他のブラウザを同じように使用することが可能になります。
さいごに
今回はISO8601以外の文字列が返ってくることを想定していないためエラーハンドリングをほとんどしていません。状況によっては、dateParseでエラーを発生させるなども必要かなと考えます。
ブラウザによる差異、最悪ですね...