萎草の茂るブログ

暇人も多忙の方も。

はてなブログのエントリやコメントの投稿時刻を絶対時刻表示にする

goo blog からはてなブログに移ってきて、はてなブログの「好きじゃないな」と思った点が、記事やコメントの投稿時刻が「○時間前」「○年前」のように相対時間表示になっているところ。
相対時間表示の優位性は理解するが、とは言え好きじゃないものは好きじゃない。
という訣で、投稿時刻を絶対時刻表示にしてみました。

どうやるの?

HTML の読込完了後に JavaScript で置換してしまいましょう。
はてなブログに表示される投稿時刻は幸い <time> タグで表現されており、その datetime 属性に絶対時刻が ISO 8601 形式で記録されているので、それを拾って表示してやれば OK です。

JavaScript のコードは、ブログの管理画面の [設定] - [詳細設定] - [head内タグ] - [<head>要素にメタデータを追加] に以下のように書いてやれば読み込まれます。

<script>
ここにコードを書く
</script>

対象は?

はてなブログで <time> タグが使用されているのは以下4種類。(だと思う。他にもあったら教えてください。)

  1. トップページ記事ページにおける記事上部の日付欄
    • 既定では記事の投稿日(絶対日付)が YYYY-MM-DD 形式で表示されている。
    • datetime 属性は YYYY-MM-DDThh:mm:ssZ 形式で絶対日時 (UTC) が記録されている。
    • <time> 要素が <a> 要素に包含されている。リンク先は同日の日別アーカイブページ。
  2. トップページや記事ページにおける記事下部の投稿時刻欄
    • 既定では記事の投稿時刻が相対時間で表示されている。
    • datetime 属性は YYYY-MM-DDThh:mm:ssZ 形式で絶対日時 (UTC) が記録されている。
    • <time> 要素が <a> 要素に包含されている。リンク先は記事ページ。
  3. トップページや記事ページにおけるコメントの投稿時刻欄
    • 既定ではコメントの投稿時刻が相対時間で表示されている。
    • datetime 属性は YYYY-MM-DDThh:mm:ssZ 形式で絶対日時 (UTC) が記録されている。
    • リンクは設定されていない。
  4. 年別/月別/日別アーカイブ、カテゴリー検索結果における記事上部の日付欄
    • 既定では記事の投稿日(絶対日付)が YYYY-MM-DD 形式で表示されている。
    • datetime 属性は YYYY-MM-DD 形式で絶対日付のみが記録されている。時刻を拾うのは困難。
      • すぐ下にある記事タイトルのリンク先から時刻を拾うことは可能だが、本稿では扱わない。
    • <time> 要素が <a> 要素に包含されている。リンク先は同日の日別アーカイブページ。

取り敢えずこれらを対象にする。

置換していく

1. トップページや記事ページにおける記事上部の日付欄

以下のコードで日付欄に datetime 属性の値(YYYY-MM-DDThh:mm:ssZ 形式の絶対日時)を表示させることができます。

// HTML 読込完了時に発火させる
window.addEventListener('DOMContentLoaded', function (){
	// <time> 要素を走査する
	document.querySelectorAll(".entry-date > a > time").forEach(function (time){
		// innerHTML を datetime 属性の値で上書きする
		time.innerHTML = time.getAttribute('datetime');
	});
});

「流石に YYYY-MM-DDThh:mm:ssZ では読みづらい」ということであれば、Date#toLocaleString メソッドを用いて以下のようにすれば、既定のロケールとタイムゾーンでの表示になります。
他の表示形式がお望みでしたら、この部分をお好みに応じて弄ってください。

// HTML 読込完了時に発火させる
window.addEventListener('DOMContentLoaded', function (){
	// <time> 要素を走査する
	document.querySelectorAll(".entry-date > a > time").forEach(function (time){
		// innerHTML を datetime 属性の値で上書きする
		time.innerHTML = (new Date(time.getAttribute('datetime'))).toLocaleString();
	});
});

……尤も、トップページや記事ページにおける記事上部の日付欄は、リンク先が日別アーカイブページですので、時刻を表示しない方が混乱が無いかもしれません。

2. トップページや記事ページにおける記事下部の投稿時刻欄

1. と殆ど同じです。Document#querySelectorAll での拾い方が異なるだけ。

// HTML 読込完了時に発火させる
window.addEventListener('DOMContentLoaded', function (){
	// <time> 要素を走査する
	document.querySelectorAll("time.updated").forEach(function (time){
		// innerHTML を datetime 属性の値で上書きする
		time.innerHTML = (new Date(time.getAttribute('datetime'))).toLocaleString();
	});
});

3. トップページや記事ページにおけるコメントの投稿時刻欄

厄介なのがコメントです。

コメント欄は HTML の読込が一旦完了した後に非同期に読み込んでいるらしく、1. や 2. と同じように DOMContentLoaded のリスナで置換処理を行ったのでは早すぎるようです。
また、コメントが多い記事には「もっと読む」ボタンが表示されますので、「もっと読む」ボタンの押下時にも改めて置換処理を行わねばなりません。

そこで、アドホックではありますが、HTML 読込完了と「もっと読む」ボタン押下を契機として置換処理を200ミリ秒毎に10回繰り返すという実装にします。
(「200ミリ秒」「10回」というのは特に根拠のある値ではないので、適当に調整してください)

// コメントの投稿時刻欄を retry 回置換する
function commentTimeToLocaleString(retry){
	// リトライ残回数が 0 以下ならば終了
	if (retry <= 0){
		return;
	}

	// 200ミリ秒後にリトライ
	setTimeout(commentTimeToLocaleString, 200, retry - 1);

	// <time> 要素を走査する
	document.querySelectorAll(".comment-metadata > time"), function (time){
		// innerHTML を datetime 属性の値で上書きする
		time.innerHTML = (new Date(time.getAttribute('datetime'))).toLocaleString();
	});
}

// HTML 読込完了時に発火させる
window.addEventListener('DOMContentLoaded', function (){
	// 初期表示時点でまず実行
	commentTimeToLocaleString(10);

	// 「もっと読む」ボタンのリスナに追加
	document.querySelectorAll(".read-more-comments > a").forEach(function (button){
		button.addEventListener('click', function (){
			commentTimeToLocaleString(10);
		});
	});
});

4. 年別/月別/日別アーカイブ、カテゴリー、検索結果における記事上部の日付欄

最後のこれは 1. や 2. とほぼ同じ。
但し、日付だけで時刻が拾える訣ではないので、あまり面白くはありません。
日付の表示形式を弄りたい場合にどうぞ。(以下のコード例では表示形式を Date#toLocaleDateString にしています)

// HTML 読込完了時に発火させる
window.addEventListener('DOMContentLoaded', function (){
	// <time> 要素を走査する
	document.querySelectorAll(".archive-date > a > time").forEach(function (time){
		// innerHTML を datetime 属性の値で上書きする
		time.innerHTML = (new Date(time.getAttribute('datetime'))).toLocaleDateString();
	});
});

「年別/月別/日別アーカイブ」「カテゴリー」「検索結果」をそれぞれ個別に設定したい場合は Document#querySelectorAll の引数を以下のようにしてください。

年別/月別/日別アーカイブ

document.querySelectorAll(".archive-heading + .archive-entries .archive-date > a > time")

カテゴリー

document.querySelectorAll(".archive-header-category + .archive-entries .archive-date > a > time")

検索結果

document.querySelectorAll(".search-result + .archive-entries .archive-date > a > time")

まとめ

以上を全部纏めると以下のようになります。

// コメントの投稿時刻欄を retry 回置換する
function commentTimeToLocaleString(retry){
	// リトライ残回数が 0 以下ならば終了
	if (retry <= 0){
		return;
	}

	// 200ミリ秒後にリトライ
	setTimeout(commentTimeToLocaleString, 200, retry - 1);

	// <time> 要素を走査する
	document.querySelectorAll(".comment-metadata > time"), function (time){
		// innerHTML を datetime 属性の値で上書きする
		time.innerHTML = (new Date(time.getAttribute('datetime'))).toLocaleString();
	});
}

// HTML 読込完了時に発火させる
window.addEventListener('DOMContentLoaded', function (){
	// 1. トップページや記事ページにおける記事上部の日付欄
	document.querySelectorAll(".entry-date > a > time").forEach(function (time){
		time.innerHTML = (new Date(time.getAttribute('datetime'))).toLocaleString();
	});

	// 2. トップページや記事ページにおける記事下部の投稿時刻欄
	document.querySelectorAll("time.updated").forEach(function (time){
		time.innerHTML = (new Date(time.getAttribute('datetime'))).toLocaleString();
	});

	// 3. トップページや記事ページにおけるコメントの投稿時刻欄
	commentTimeToLocaleString(10);
	document.querySelectorAll(".read-more-comments > a").forEach(function (button){
		button.addEventListener('click', function (){
			commentTimeToLocaleString(10);
		});
	});

	// 4. 年別/月別/日別アーカイブ、カテゴリー、検索結果における記事上部の日付欄
	document.querySelectorAll(".archive-date > a > time").forEach(function (time){
		time.innerHTML = (new Date(time.getAttribute('datetime'))).toLocaleDateString();
	});
});

実際このブログではどのように設定しているのか?

このページのソースコードを読んでください。
(このブログでは、クライアントのロケールに依らず UTC+9 で表示させるために若干面倒なことをしています)

参考資料

コメントの投稿時刻欄については以下リンク先を参考にしました。
リンク先のコードでは、ページ初期表示時において「コメント欄が表示されるまで試行を繰り返す」という実装になっています。
ただ、残念乍ら「もっと読む」ボタン押下時については対応していません。

q.hatena.ne.jp