LoginSignup
4
2

More than 5 years have passed since last update.

SafariにおけるAngularのDatePipeでISO8601がパースできない問題をカスタムパイプで解決する

Last updated at Posted at 2017-01-29

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)

のようなエラーが発生します。

Screen_Shot_2017-01-29_at_11_37_46.png

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でエラーを発生させるなども必要かなと考えます。

ブラウザによる差異、最悪ですね...

4
2
1

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
4
2