はまったりひらめいたり…とか…

.Net系プログラムで勉強したこととか嵌ったことについて書いたりします。

Angular2で和暦フォーマットDatePipeを作る。

業務で使っているだけあって、Angular2の知見が溜まってきたので放出放出。

こんな感じの表示ができるPipeを作りました。というお話です。

使用したのは「Angular2 R.C5」でございます。

f:id:TakasDev:20160812041422p:plain

Pipeってなんぞや

Angularでデータバインドする際、下記のように記述することで

Date型の値がFormatされて出力されるようになります。

{{ nowDate | date:"yyyy/MM/d" }}

↓こんな感じに表示されます。

f:id:TakasDev:20160812041012p:plain

Date型にかぎらず

例えば数値に対応する文字列

value=10000→画面上は「福沢諭吉」と表示

value=5000→画面上は「新渡戸稲造」と表示することもできるわけです。

和暦DateFormat

.Netは和暦もフォローしてくれていますが

Webは和暦をフォローしてくれません。

なので、和暦フォーマットファンクションを作成し

それを使用したDatePipeを作成してみます。

date-jp-format.ts(V1)

export class DateJpFormat {
    GetJpFormatDate(value: Date, format: string): string {
        if (!format) format = "yyyy年 MM月 DD日 aa曜日 hh:mm:ss";
        //和暦を取得
        var jpDate = value.toLocaleDateString("ja-JP-u-ca-japanese");
        var jpYear = jpDate.substring(0, jpDate.indexOf("/"));
        var jpYearArray = jpYear.match(/[0-9]+\.?[0-9]*/g);
        var jpYearNumber = "";
        //SafariなんかはArray=Nothingでくる!
        if (jpYearArray) {
            jpYearNumber = jpYearArray[0];
        }
        var regExpg = new RegExp(jpYearNumber, "g");

        var jpYearString = jpYear.replace(regExpg, "");
        var jpYearStringShort = jpYearString.slice(0, 1);

        //年変換(Full桁)
        format = format.replace("gg", jpYearString);
        format = format.replace("yyyy", ("" + (value.getFullYear())));

        //年変換(短縮)
        format = format.replace("g", jpYearStringShort);
        format = format.replace("yy", ("0" + (value.getFullYear())).slice(-2));
        format = format.replace("e", jpYearNumber);

        format = format.replace("MM", (("0" + (value.getMonth() + 1)).slice(-2)));
        format = format.replace("DD", (("0" + (value.getDay())).slice(-2)));
        format = format.replace("aa", weekDayList[value.getDay()]);
        format = format.replace("hh", (("0" + (value.getHours())).slice(-2)));
        format = format.replace("mm", (("0" + (value.getMinutes())).slice(-2)));
        format = format.replace("ss", (("0" + (value.getSeconds())).slice(-2)));
        return format;
    }
}
var weekDayList = ["日", "月", "火", "水", "木", "金", "土"];

とりあえず、DatePipeは抜きにして、どのように変換されるのかみてみます。

f:id:TakasDev:20160812041057p:plain

「ja-JP-u-ca-japanese」で元号「平成」等を表示してくれるのは

Chormeで、IEやEdgeなんかは、元号は取得してくれません(FireFoxは試してないです)。

Safariに至っては和暦の年数すら取得できません。

ベースはこれでいいかもしれませんが、元号が取れなかった場合のことを考え

自前で実装する他なさそうです。

Formatの実装

1. 和暦に関する情報を準備

和暦換算に必要な情報を作成します。 ・jp-calender-info.ts

export const jpCalendarInfo: JpCalenderType[] = [
    {
        Name: "明治",
        StartDate: new Date("1868-09-08T00:00:00"),
        EndDate: new Date("1912-07-30T00:00:00")
    },
    {
        Name: "大正",
        StartDate: new Date("1912-07-30T00:00:00"),
        EndDate: new Date("1926-12-25T00:00:00")
    },
    {
        Name: "昭和",
        StartDate: new Date("1926-12-25T00:00:00"),
        EndDate: new Date("1989-01-08T00:00:00")
    },
    {
        Name: "平成",
        StartDate: new Date("1989-01-08T00:00:00"),
        EndDate: new Date("2087-12-31T23:59:59")
    }
];

export class JpCalenderType{
    Name: string;
    StartDate: Date;
    EndDate: Date;
}

2. 和暦算出ファンクションの作成

1で作成した構造体を、算出した日付でフィルタを掛け

和暦の元号と年数の算出を行うファンクションを作成します。

    private FormatJpYearString(formatDate: Date): string {
        var jpInfo = jpCalendarInfo
            .filter((x:JpCalenderType) => x.StartDate <= formatDate && formatDate < x.EndDate);
        return jpInfo[0].Name;
    }
    private FormatJpYearNumber(formatDate: Date): string {
        var jpInfo = jpCalendarInfo
            .filter((x:JpCalenderType) => x.StartDate <= formatDate && formatDate < x.EndDate);
        var nowYear = formatDate.getFullYear();
        var baseYear = jpInfo[0].StartDate.getFullYear() - 1;
        return String(nowYear - baseYear);
    }

3.date-jp-format.tsの修正

上記のファンクションを合わせると、date-jp-format.tsは下記のようになります。

今回作成したファンクションは応急処置的な作りになっていますので

基本的にはブラウザ側で取得できたら、その値を使用する感じになっています。

ここで問題なのが「jpYearString 」にIEの場合ナニが入っているのか?ということです。

Emptyに見えますが実は「」という文字が入ってます。

ハイ。見えませんね。

ここにはUTF-16のU+200E文字コードの文字が入っているようです。

今回、かなり力技で出力された見えない文字をコピって貼り付けてますが

本来であれば文字コード変換等を行って、U+200E文字を潰すなりしなければならないはずです。

date-jp-format.ts

import { jpCalendarInfo, JpCalenderType } from "../function/jp-calender-info";

export class DateJpFormat {
    GetFormatDate(getValue: any, format: string): string {
        //取得した値を日付変換
        var value = new Date(getValue);

        //Format未指定の場合は西暦表示
        if (!format) format = "yyyy年 MM月 DD日 aa曜日 hh:mm:ss";
        //和暦を取得
        var jpYearNumber = this.FormatJpYearNumber(value);
        var jpYearString = this.FormatJpYearString(value);
        var jpYearStringShort = jpYearString.slice(0, 1);

        //年変換(Full桁)
        format = format.replace("gg", jpYearString);
        format = format.replace("yyyy", ("" + (value.getFullYear())));

        //年変換(短縮)
        format = format.replace("g", jpYearStringShort);
        format = format.replace("yy", ("0" + (value.getFullYear())).slice(-2));
        format = format.replace("e", jpYearNumber);

        format = format.replace("MM", (("0" + (value.getMonth() + 1)).slice(-2)));
        format = format.replace("DD", (("0" + (value.getDate())).slice(-2)));
        format = format.replace("aa", weekDayList[value.getDay()]);
        format = format.replace("hh", (("0" + (value.getHours())).slice(-2)));
        format = format.replace("mm", (("0" + (value.getMinutes())).slice(-2)));
        format = format.replace("ss", (("0" + (value.getSeconds())).slice(-2)));
        return format;
    }
    private FormatJpYearString(formatDate: Date): string {
        var jpInfo = jpCalendarInfo
            .filter((x: JpCalenderType) => x.StartDate <= formatDate && formatDate < x.EndDate);
        return jpInfo[0].Name;
    }
    private FormatJpYearNumber(formatDate: Date): string {
        var jpInfo = jpCalendarInfo
            .filter((x: JpCalenderType) => x.StartDate <= formatDate && formatDate < x.EndDate);
        var nowYear = formatDate.getFullYear();
        var baseYear = jpInfo[0].StartDate.getFullYear() - 1;
        return String(nowYear - baseYear);
    }
}

var weekDayList = ["日", "月", "火", "水", "木", "金", "土"];

結果、Edgeでも下図のように表示されるようになりました。

f:id:TakasDev:20160812041231p:plain

4.DatePipeを作る

「@angular/core」の「Pipe」「PipeTransform 」を使用します。

@Pipe以降の「name」でHTML上に記述する、Pipeの名称を設定します。

transform()内部は表示するデータのFormatを行っている部分です。

今回は日付のデータと文字列を使用したフォーマットを行います。

結果、下記のようなソースコードとなります。

jp-date-pipe.ts

import { Pipe, PipeTransform } from "@angular/core";
import { DateJpFormat } from "../function/date-jp-format"

@Pipe({ name: "JpDatePipe" })
export class JpDatePipe implements PipeTransform {
    dateJpFormat: DateJpFormat;
    transform(value: Date, args: any[]) {
        if (value) {
            this.dateJpFormat = new DateJpFormat();
            var formatString = String(args);
            return this.dateJpFormat.GetJpFormatDate(value, formatString);
        }
    }
}

5.DatePipeを使う

今回作成したPipeを使用したいコンポーネント

「独自設定したPipeを使いますよ」と設定しなければ当然使えません。

{pipes: [JpDatePipe]}で使用するPipeを宣言することで使えるようになります。

下記のような記述で使用します。 {{nowDate | JpDatePipe:[Formatする文字列]}}

最終的に下記のようなソースコードになりました。 date-format-page.component.ts

import { Component, Pipe } from "@angular/core";
import { DateJpFormat } from "../../function/date-jp-format";
import { JpDatePipe } from "../../custom-pipe/jp-date-pipe";
@Component({
  pipes: [JpDatePipe],
  template: `
    <h2>↓変換日付↓</h2> 
    {{nowDate}}
   <h2>↓DatePipe変換↓</h2>
    <div *ngFor="let outputFormat of formats">
      {{nowDate | JpDatePipe:[outputFormat]}}
    </div>
  `
})
export class DateFormatPageComponent {
    nowDate: Date;
    formats: string[];
    _dateJpFormat: DateJpFormat;
    constructor(){
        this.nowDate = new Date();
        this.formats= [
            "gg",
            "yy",
            "yyyy",
            "gge",
            "ge",
            "gge年MM月",
            "yyyy年MM月",
            "gge年MM月DD日",
            "yyyy年MM月DD日",
            "gge年MM月DD日aa曜日",
            "yyyy年MM月DD日aa曜日",
            "gge年MM月DD日aa曜日(hh)",
            "yyyy年MM月DD日aa曜日(hh)",
            "gge年MM月DD日aa曜日(hh)",
            "yyyy年MM月DD日aa曜日(hh)",
            "gge年MM月DD日aa曜日(hh:mm)",
            "yyyy年MM月DD日aa曜日(hh:mm)",
            "gge年MM月DD日aa曜日(hh:mm:ss)",
            "yyyy年MM月DD日aa曜日(hh:mm:ss)",
            "本日、gge年MM月DD日aa曜日です!!",
        ];
    }   
}

やたらと長いですが、結構な数のFormatを試しているので長くなっています。

肝の部分は下記のHTMLですね。

<h2>↓DatePipe変換↓</h2>
<div *ngFor="let outputFormat of formats">
    {{nowDate | JpDatePipe:[outputFormat]}}
</div>

Tsで宣言したFormatをすべて表示するHTMLとなります。

JpDatePipeという一つの変数から、ありとあらゆる表示の仕方が可能となっています。

結果、Webページ上には下図のように表示されました。

f:id:TakasDev:20160812041422p:plain

まとめ

上記のとおり、日付情報自体は同じ変数を使いまわしてるので

Pipeを使えばView用の変数と登録用の変数を分ける…

なんてことはしなくても良くなるということですね。

便利。