デベロッパー

デベロッパー

時差を考慮した日時の格納と表示

Scott Mitchell
2010年7月27日 / 10:00
 
 

はじめに

クライアント、同僚、そして4Guys の読者から良く求められるのが、データ駆動式 Web アプリケーションにおける日付や時間の最適な格納および表示方法に関するアドバイスだ。 Web アプリケーションに日付を格納および表示する際に難しいことの1つが、サイトを訪れる訪問者の地域の時間帯がウェブサーバがある地域のそれと同じとは限らないという問題だ。あなたのサイトには、かなりの確率で世界中の多くの異なる時間帯の地域から訪問者が集まっている。

100万件以上の書き込みのすべてに作成日時が付いているASPMessageboard.comのようなオンライン掲示板サイトを考えてみたい。ニューヨークのユーザーが4月7日の午後4:30に書き込みをし、同サイトをホスティングするウェブサーバはニューヨークより1時間早いテキサス州ダラスにある場合を想像したい。この書き込みをデータベースに格納するときは、訪問者から見た日時(4:30 PM)か、ウェブサーバから見た日時(3:30 PM)か、それともほかの値を記録するのか? また、この書き込みをニューヨークより3時間早いカリフォルニア州サンフランシスコのユーザーに表示するときの日時はどうするのだろうか? 表示するのは投稿者から見た時間(4:30 PM)か、ウェブサーバから見た時間(3:30 PM)か、それともユーザーから見た時間(1:30 PM)するのだろうか? さらに、投稿者や訪問者の時間帯を基準にしてこの日付を格納もしくは表示することにしたなら、どうやってその時間帯と時差を知るのだろうか? 夏時間などはどう調整するのだろうか? 

本稿は異なる時間帯をまたいだ訪問者に対する日時の格納および表示方法の指針を提供し、これらのテクニックの一部が実際に動いている例を示すデモも用意する。ぜひ詳細をお読みいただきたい。

日時の格納

日付の格納が必要なデータ駆動式の Web アプリケーションを構築するときに最初に下さなければならない判断は、この日時をデータベースに格納するときの方法だ。たいてい、データベースシステムは日時情報の格納に最低1つのデータタイプを提供する。Microsoft SQL Server では以前から「datetime」という名前のデータタイプを提供しているが、これは日付(1753-01-01から9999-12-31まで)と時間(0.00333秒の精度の時、分、秒、およびマイクロ秒)の両方を格納する。さらに、「smalldatetime」 データタイプもあり、これは狭い範囲の日付値(1900-01-01から2079-06-06まで)と分精度の時間を格納する。SQL Server 2008には、時間だけを格納するデータタイプ(「time」)、日付だけを格納するデータタイプ(「date」)、そして大幅に広い範囲の日付とさらに精度の高い時間を格納するデータタイプ(「datetime2」)など、多数の新しい日付/時間タイプが搭載されている。本稿では、「datetime」 データタイプの使用に重点を置くが、ここで解説する概念は「smalldatetime」と「datetime2」 の両データタイプにも対応する。SQL Server 2008に追加されたこれらの新しい日付/時間データタイプの詳細は、New Date Data Types in Microsoft SQL Server 2008(Microsoft SQL Server 2008の新しいデータタイプ)を参照のこと。

日時情報を格納するデータタイプを決めたら、次は日時をそのまま格納するか、時間帯に合わせて格納するかを決める必要がある。ユーザーに日時を入力させるのか、あるいは日時が特定の地理的な位置に結びついている場合は、たぶん日時はそのまま格納したいだろう。カンファレンスプランナー向けのウェブサイトを構築中だとする。カンファレンスプランナーにあなたのウェブサイトを使って彼らのカンファレンスに関する情報(開催場所、日時、各種セッション、講演者など)を入力させ、顧客にこのサイトを使ってカンファレンスサービスを吟味し、予約させるなどすることが狙いになる。カンファレンスプランナーがカンファレンス情報を入力する画面では、カンファレンスの開始日時と終了日時を入力する必要がある。また、各セッションの日時も提供する必要がある。おそらく、ユーザーが入力した日時の値をそのまま格納したい場合が多いだろう。つまり、ユーザーが特定のセッションの開始日時(2010年4月10日の1:30 PM など)を入力する時は、その実際の値をデータベースに格納する。入力された日付/時間をウェブサーバやそのほかの時間帯に合わせないこと。同様に、これらの日付/時間の値を表示するときは、そのままの値を表示し、訪問者の時間帯に合わせて変えないこと。

掲示板サイトに投稿があったときの日時など、日時が自動生成される場合は、時間帯に合わせた日時の値を格納する必要がある。SQL Server の「getdate () 」ファンクションや.NET の「DateTime.Now」プロパティを使って日時の値を保存する場合は、ウェブサーバの時間帯に合わせた日時の値を格納することになる。これは確かに便利だがマイナス面もある。まず、あなたのアプリケーションのデータがそのウェブサーバの時間帯限定のものになってしまう。異なる時間帯にあるウェブホスティング会社に乗り換えた場合に何が起こるのか考えたい。新しい投稿では新しい時間帯が反映されるが、既存の投稿はすべて古い時間帯を示したままになる。

もっと良いアプローチとしては、時間を協定世界時(UTC)で格納する方法がある。これは、すべての時間帯をオフセットで表す標準国際時間となっている(また、UTC は夏時間調整を行わない)。日付/時間の値を UTC で核のする主な利点は、特定の時間帯に縛られなくなるためデータが可搬型になることだ。SQL Server で現在の日時を UTC で取得するには、「getutcdate () 」ファンクションを使う。.NET では、「 DateTime.UtcNow」を使う。UTC と、Web アプリケーションで UTC を使うメリットの詳細は、「Using Coordinated Universal Time (UTC) to Store Date/Time Values」(協定世界時を使った日付/時間の値の格納)を参照。

一般的な経験則として、ユーザーに日付/時間の値を入力させる場合は、当てはまる時間帯に合わせた日時を入力している可能性が高いため(カンファレンスやフライトのスケジューリングなど)、日付をそのまま格納する。しかし、日付/時間の値が自動的に計算される場合(最も一般的なのは現在の日時)は、日付/時間の値をウェブサーバの時間帯や訪問者の時間帯ではなく UTC で格納するのが最良だと思われる。

日付と時間の表示

訪問者に対して日時を表示するときは、日時を訪問者の時間帯に合わせるかどうかを決める必要がある。そのまま入力した日付/時間、あるいは特定の地理的な位置に結びつけられた日付/時間(飛行機のフライトやカンファレンスセッション)は日付をそのまま表示し、訪問者の時間帯に合わせてはならない。時間帯オフセット(UTC あるいはウェブサーバの時間帯)に合わせた日時には表示に関して3つの選択肢がある。
  1. 日付/時間をそのまま表示する。シンプルだが、日時の値がさほど重要でない Web アプリケーションにしか推奨できない。また、日時を調べるユーザーが混乱する可能性があることにあらかじめ注意する。たとえば、掲示板アプリケーションで日時を UTC で格納し、それをそのまま表示すると、新しい投稿が西半球の時間帯にいるユーザーには考えられない時間を表示することになる(カリフォルニアにいるサマータイム中のユーザーは UTC より7時間早いので、投稿があったときから7時間は、カリフォルニアのユーザーは未来の投稿日時を目にすることになる)。
  2. 日付/時間を相対表示する。ユーザーに正確な日時を表示せず、相対的な日時を表示する。多くの掲示板サイトはこの手法を用いている。「2010年4月7日3:30 PM 投稿」と表示される代わりに、ユーザーには「5分前に投稿」もしくは「約3時間前に投稿」、あるいは 「2週間前に投稿」のようなメッセージが表示される。これは日時の時間部分がデータの経過時間ほど重要でない Web アプリケーションでうまく機能する。
  3. 格納された日付/時間を訪問者の時間帯に合わせて表示する。 このアプローチでは、日付/時間の値を訪問者の時間帯に合わせて表示する。これを実現するには以下のいずれかが必要になる。
    1. (表示前に時間を調整できるよう)データを格納した時間帯とのユーザーの時間帯オフセットを判断する。あるいは、
    2. 時間を UTC で送信し、JavaScript を使ってユーザーのコンピュータ設定に基づく現地時間を表示する。
本稿の残りの部分では、選択肢の1、2、および3の b を使った日時の表示方法を探求していく。これらのオプションについて見ていく前に、本稿の最後でダウンロード先を紹介するデモを簡単に見てみよう。このデモプロジェクトは非常に簡単な来賓名簿アプリケーションとなっている。手短に言うと、これには訪問者が入力する来賓名簿の項目ごとに1つのレコードを格納する「Entries」というテーブルを1つ持つ SQL Server 2008 Express Edition データベースがある。このテーブルは、「datetime」タイプとなっている「EntryDate」列で来賓名簿への入力が行われた日時を格納し、デフォルト値はgetutcdate () となっている。この「EntryDate」の値はユーザーが入力するものではない。来賓名簿へ記入を行う際、ユーザーが入力するのは自分の名前とメッセージだけで、「EntryDate」の値は現在の UTC 日時に自動的に設定される。来賓名簿の項目はListView コントロールを使って表示される。ItemTemplate では、「EntryDate」の値が「DisplayDate」という名前のメソッドに渡され、それがドロップダウンリストで指定されたフォーマットにしたがってデータをフォーマットして返す。

日時をそのまま表示する

日時をそのまま表示する際は、たいていの場合は文化的なあいまいさをすべて排除するフォーマットを使うのがベストだ。米国では、日付は MM/DD/YY で表示されることが多いが、ほかの多くの国では DD/MM/YY のフォーマットを使う。あいまいな部分をすべて排除するために、以下のカスタム日付/時間フォーマット指定子を使って月の名前を表示に加えることを推奨する。
  • MMMM d, yyyy h:mm tt、これは月、日、そして年を表示し、それに続けて12時間制の時間と AM / PM 識別子を表示し、以下のようになる。 April 7, 2010 2:15 PM
  • d MMM yyyy h:mm tt、これは日、月の短縮形、そして年に続けて12時間制の時間と AM / PM 識別子を表示し、以下のようになる。 7 Apr 2010 2:15 PM
さらに詳しい情報や、自分独自のカスタムフォーマット指定子を作成する場合は、「Custom Date and Time Format Strings」(カスタム日時フォーマット文字列)を参照されたい。

DateTime」 ストラクチャのToString ("フォーマット指定子")オーバーロードもしくはString.Formatメソッドを使うことにより、以下のコードで示されるようにこの方法で日時を出力することができる。
文字列 formattedDateTime = String.Format("{0:d MMM yyyy h:mm tt }", dateTimeVariable);

- or -

string formattedDateTime = dateTimeVariable.ToString("d MMM yyyy h:mm tt");


日時を相対的に表示する

データの相対経過日時の方が絶対日時より重要なシナリオでは、日時の値を相対値で示すことを考えたい。掲示板の例に戻ると、土壇場で行われた投稿の日時の値を言葉で示すと、「1分以内」というようになる。1時間以内に行われた投稿の時間は、「○○分前」というようになり、「○○」は投稿時点からの経過分数となる。投稿が古くなるにつれ、正確な早退時間は重要でなくなっていく。1週間以上は経過したが1カ月は経過していないという投稿は「3週間前」のように表示することができる。このような文章は確かにあまり正確ではないが、このような精度は古い投稿にとってあまり重要でない。また、1カ月以上が経過した投稿はもしかすると投稿日だけ表示し、時間は完全に削除するかもしれない。

このようにするには、格納された日時の値と現在の日時の値の差を判断する簡単なコードを書く必要がある(日時の値を UTC で格納した場合は、ウェブサーバの現在の日時ではなく、現在の UTC 日時と格納された値とを必ず比較する必要がある)。筆者はこのようなコードを相当数のプロジェクトで使ってきたため、「DateTime」ストラクチャの拡張メソッドを作成している。具体的に、筆者は「ToRelativeDateString () 」および「ToRelativeDateStringUtc () 」の2つのパブリックメソッドを追加している。これらは、現在の現地時間(あるいは現在の UTC 時間)と比較を行ったときに、「DateTime」の値に対する相対経過日時を返す。

拡張メソッドは、VB や C#の.NET Framework 3.5付属バージョンから登場した。きわめて簡潔に言うと、拡張メソッドを使えば特定のタイプで定義されたかのように扱われるメソッドを作成できるようになる。自分が作成した拡張メソッドを使うことで、筆者は以下のようなコードを書くことができる。
DateTime dt = new DateTime(2010, 4, 7, 9, 50, 30);

string formattedDateTime = dt.ToRelativeDateStringUtc();
コードの1行目では「dt」という名前の変数を作成し、それを「April 7, 2010 9:50:30」の新しい「DateTime」オブジェクトに割り当てる。次に、「DateTime」構造本体で定義されたメソッドのようにコールすることで自分の「ToRelativeDateStringUtc () 」拡張メソッドを呼び出す。「ToRelativeDateStringUtc () 」メソッドは相対経過日時(「8分前など」)の入った文字列を戻す。拡張メソッドの作成と使用に関する詳細はExtending Base Type Functionality with Extension Methods(拡張メソッドによる基本タイプ機能の拡張n)を参照のこと。

拡張メソッドの定義が続き、これはデモプロジェクトの「App_Code」フォルダにある。
namespace skmExtensions
{
   public static class DateTimeExtensions
   {
      public static string ToRelativeDateString(this DateTime date)
      {
         return GetRelativeDateValue(date, DateTime.Now);
      }

      public static string ToRelativeDateStringUtc(this DateTime date)
      {
         return GetRelativeDateValue(date, DateTime.UtcNow);
      }

      private static string GetRelativeDateValue(DateTime date, DateTime comparedTo)
      {
         TimeSpan diff = comparedTo.Subtract(date);

         if (diff.Days >= 7)
            return string.Concat("on ", date.ToString("MMMM dd, yyyy"));
         else if (diff.Days > 1)
            return string.Concat(diff.Days, " days ago");
         else if (diff.Days == 1)
            return "yesterday";
         else if (diff.Hours >= 2)
            return string.Concat(diff.Hours, " hours ago");
         else if (diff.Minutes >= 60)
            return "more than an hour ago";
         else if (diff.Minutes >= 5)
            return string.Concat(diff.Minutes, " minutes ago");
         if (diff.Minutes >= 1)
            return "a few minutes ago";
         else
            return "less than a minute ago";
      }
   }
}
All the 興味深い作業はすべて「GetRelativeDateValue」メソッドで行われ、これは拡張メソッドがコールされた「DateTime」の値と、渡された「comparedTo DateTime」の値(現在のローカルもしくは UTC 日時のいずれかにになる) の差を計算する処理から始まる。次に、さまざまな測定基準をチェックして、戻すのに適した文字列を判断する。この拡張メソッドは自分のプロジェクトで自由に使い、必要に応じて拡張していただいてかまわない。

JavaScript を使って訪問者の現地時間で日時を表示する

JavaScript は日時の作成、操作、および表示のためのさまざまなファンクションを提供する(これらのファンクションを浮き彫りにするPatrick Hunlock 氏のブログには、素晴らしい参考資料がそそっている。Javascript の日付 - 完全ガイド)。JavaScript では、時間帯を指定して文字列を渡すことで日付を作成することができる。そして、「toLocaleString () 」ファンクションを使ってその時間をユーザーの現地時間帯に変換することができる。

この概念を例証するため、以下の短い JavaScript コードを考える。
<script type="text/javascript">
   var dt = new Date(’ April 7, 2010 3:02 PM UTC ’);
   var formattedDateTime = dt.toLocaleString();
   document.write(formattedDateTime);
</script>


上の内容をウェブページに入れて、そのページをブラウザで表示させると、その場所に来たときに JavaScript が即座に実行される。スクリプトは、新しい「Date」オブジェクトに割り当てられた「dt」という変数の作成から処理を開始する。ご覧のように、「Date」オブジェクトを作成するときは、日時の値を文字列として指定することができ、それを JavaScript がパースする。また、この文字列には時間帯(この場合はUTC)が含まれることに注意する。

スクリプトの次の行では、この Date オブジェクトを取って「toLocaleString () 」ファンクションをコールすると、それが日付/時間の値を現在の時間帯の文字列として返す。つまり、このページを訪れるユーザーが UTC より2時間早い時間帯にいる場合は、「toLocaleString () 」ファンクションが時間を「5:02 PM」として伝える。UTC から3時間遅れのユーザーの場合は、ファンクションが時間を「12:02 PM」として伝える。そして最後に、「document.write」ファンクションが「formattedDateTime」 変数の値をブラウザに送信する。上のコードを筆者のコンピュータ(UTC の7時間遅れ)で実行すると。以下のような朱 t ぐりょくが得られる。 Wednesday, April 07, 2010 8:02:00 AM.(ローカルのに付けもしくは時間だけを送信する場合は、「toLocaleDateString () 」もしくは「toLocaleTimeString () 」ファンクションを使う。)

デモでは、ユーザーが来賓名簿の項目の日時を現地時間で見たい場合は以下のコードが実行される。
string formattedDateTime = String.Format(
   "<script type=?"text/javascript?">
       document.write(new Date(’{0:MMMM d, yyyy hh:mm:ss tt } UTC ’).toLocaleString());
   </script>"
   , dateTimeVariable);
ここでは、dateTimeVariable は現在表示中の来賓名簿項目の「EntryDate」の値になっている。「formattedDateTime」の値は ListView の ItemTemplate に送られる。このデモをダウンロードすると、Alexander が入力した来賓名簿項目が見える。特定の来賓名簿項目の ListView で送信されたマークは以下のようになる。
<div class="entry">
   This website is quaint. A guest book! How 1998ish!
</div>

<div class="entryMeta">
   ... Entry left by <span class="entryLeftBy">Alexander</span>
   <script type="text/javascript">
      document.write(new Date(’ April 6, 2010 05:11:04 PM UTC ’).toLocaleString());
   </script>
</div>
Alexander の書き込みは2010年4月6日の午後05:11:04 UTC で行われた。この日付の値と適切なスクリプトがブラウザに対してどのようにレンダリングされるのかに注意したい。ブラウザがこのコンテンツをパースすると、これが適切なローカル日時を表示する。以下のスクリーンショットは、UTC よりさらに1時間遅れの筆者のコンピュータで見たときのこの来賓名簿項目を示している。


まとめ

データ駆動式 Web アプリケーションの多くは日時の値を格納および表示する必要がある。このようなアプリケーションにおける懸案事項懸念の1つが、サイトを訪問するユーザーがさまざまな時間帯にいることを考慮して、これらの日付/時間値の最適な格納方法だ。特定のアプリケーションでは時間をそのまま格納および表示するのが最適だ。航空券の予約やカンファレンスのスケジューリングなど、日時が地理的位置固有のものになるアプリケーションは、大抵はすべてが当てはまる。一方、日時の値が自動的に割り当てられる(たいていは現在の日時)場合は、日時を UTC で格納するのが最適な場合が多い。日時の値を表示するには、そのまま表示するか、相対経過日時を使うか、あるいは日時を訪問者の時間帯に合わせる。日時を訪問者の時間帯に合わせる場合は、UTC (あるいは日付/時間の値が格納された特定の時間帯)との時間差を指定させるか、JavaScript を使って格納された時間を訪問者の現地時間に自動変換する。

プログラミングをお楽しみあれ。

著者紹介

Scott Mitchell(Scott Mitchell)
 
【関連記事】
Java Web サービスで Restlet ルーティングシステムを活用する
日本ベリサイン、クラウド WAF サービス「Scutum」の販売を開始
富士ソフト、Azure で会社情報サービスサイトを開始
F5、日本 IBM のクラウド基盤に BIG-IP を提供
IBM が Web 分析会社の Coremetrics を買収へ

New Topics

Special Ad

ゆりかごからロケットまで、すべての乗り物をエンジョイ
ゆりかごからロケットまで、すべての乗り物をエンジョイ えん乗り」は、ゆりかごからロケットまで、すべての乗り物をエンジョイする、ニュース、コラム、動画などをお届けします! てんこ盛りをエンジョイするのは こちらから

Hot Topics

IT Job

Interviews / Specials

Popular

Access Ranking

Partner Sites