タイムポイント

ある程度の粒度で時間を表す

これは、私が2000年代半ばに行っていたエンタープライズアプリケーションアーキテクチャのさらなる開発に関する記述の一部です。残念ながら、他の多くのことに気を取られ、それ以上作業する時間がありませんでしたし、近い将来も時間を見つけることは難しいでしょう。そのため、この資料は非常にドラフトの形式であり、再び作業する時間が見つかるまで、修正や更新を行う予定はありません。

タイムポイントは、言うまでもなく、時間の点です。私はこれを2000年8月28日に書いています。それがタイムポイントです。ではなぜ、パターンとしてタイムポイントについて書くのでしょうか?結局のところ、それらは多くの言語やほとんどのクラスライブラリの一部となっています。

問題は、タイムポイントには、ライブラリを構築する人々にとっても明らかではない、いくつかの微妙な点があることです。

仕組み

タイムポイントに関する最も一般的な問題は、さまざまなレベルの精度で存在することです。私がこれを2000年8月28日に書いていると言うときと、2000年8月28日午後2時33分34秒に書いていると言うときとでは、2つの異なることを言っています。1つのステートメントは日の精度であり、もう1つは秒の精度です。秒の精度は日の精度よりも正確ですが、この場合は精度が低くなります。任意のタイムポイントは、その精度を知る必要があります。これにより、このイベントが別のイベントと同じ時間に発生したかどうかなどの質問に答えることができます。

重要な点は、ほとんどのドメインでは精度だけに頼ることができないということです。ビジネスの多くは日の精度で行われます。私が送金のリクエストを電話で行ったのが、その日のいつであるかは関係ありません。私がそれを行った日に従って処理されるだけです。そうでなければ、生活は非常に面倒なことになる可能性があります。請求書の支払いを提示された同じ日に送金したい場合、請求書が提示された正確な時間を気にする必要があるでしょうか。一般的なビジネス慣行では、同じ日であれば、口座が過剰になったり、支払いを拒否されたりするリスクはないとされています。

必要以上に正確なタイムポイントの使用には注意してください。多くのプラットフォームでは、秒以上の精度を持つタイムポイントのみが提供されます。では、2000年8月28日の任意の時間をどのように表現すればよいでしょうか?多くの場合、午前0時「00:00:00」というような規則を使用します。それは状況によってはうまくいくかもしれませんが、問題が発生します。タイムポイントに数ミリ秒を漏らすことは驚くほど簡単です。その時点で、2000年8月28日は2000年8月28日と等しくないため、問題が発生します。

タイムポイントに関するもう1つのトリッキーな領域は、タイムゾーンをどのように処理するかです。精度と同様に、すべてのアプリケーションにとって正しい答えはありません。タイムゾーン付きのタイムポイントが必要な場合もあれば、そうでない場合もあります。タイムゾーンのないタイムポイントは、そのコンテキストのローカル時間内を意味するため、完全に理にかなっています。これらは、追加のタイムゾーン情報が役に立たないか、そのコンテキストから取得できる場合に見つかります。必要のない場合は、タイムゾーンの使用に注意してください。

この問題の例として、MicrosoftのOutlookで非常にイライラさせられたことがあります。入力した予定時間(少なくともOutlook 98の場合)はタイムゾーン固有でした。そのため、タイムゾーンを移動してラップトップの時間を変更すると、会議の時間が変更されました(最初にOutlookに時間を入力するときに考慮に入れた場合を除きます。これはほとんどの人が行うことではありません)。さらに、終日の会議を選択した場合、その日を日の精度のタイムポイントとして保持せず、タイムポイントの範囲を選択しました。そのため、ボストンで入力した終日会議は、シカゴに飛んだときには午後11時から午後11時になりました。さらに悪いことに、私のWinCEマシンは終日の会議を範囲の開始を使用して日の精度で表示したため、終日の予定は前日に移動しました。

タイムポイントに関する私の議論では、タイムポイントの重要な機能は、アンカーされていることです。つまり、タイムゾーンはタイムライン上の特定の点を指します。値が午後2時30分の時間オブジェクトは、任意の日付の午後2時30分を意味する可能性があるため、アンカーされていません。午後2時30分のコンテキストを提供する日の精度のタイムポイントがある場合、午後2時30分は、日の精度の時間とともに、(適切にアンカーされた)タイムポイントを表します。

タイムポイントクラスの明白で一般的なサービスは、現在のタイムポイントを取得することです。通常、これはオペレーティングシステムを使用してシステムクロックを照会することによって行われます。ただし、ここに間接を追加することをお勧めします。テストでは安定した時間が必要になることが多く、操作では先週の金曜日と同じように月曜日にシステムを実行する必要がある場合もあります。したがって、システム日付にアクセスするだけでなく、処理日にもアクセスする価値があります。これは同じである場合とそうでない場合があります。さらに、処理日は操作中に設定可能である必要があります。ロギングを使用する場合は、ログで処理日とシステム日付の両方を使用する必要がある場合があります。

いつ使用するか

タイムポイントは、何らかの形で、ほとんど常に使用されます。それらを使用する際の重要な質問は、上記で説明した精度とタイムゾーンの問題について決定することです。ドメインで必要な場合にのみ日の精度のタイムポイントを使用し、独自のラッパークラスを作成する必要がある場合でも、規則や範囲でごまかそうとしないでください。同様に、本当に必要な場合を除いてタイムゾーンを使用しないでください。システムを使用している人が世界をどのように見ているかを考えてください。多くの人は、必要がない限りタイムゾーンについて考えません。

例:簡単な日付精度のラッパー(Java)

ここに、私がここで例として使用している簡単な日付精度ラッパーの一部を示します。基本的な構造は、JavaのGregorian Calendarインスタンスをラップします。

class MfDate...

  private GregorianCalendar _base;
public MfDate() {
  this(new GregorianCalendar());
}
public MfDate(int year, int month, int day) {
  initialize (new GregorianCalendar(year, month - 1, day));
}
  private void initialize (GregorianCalendar arg) {
      _base = trimToDays(arg);
  }
  private GregorianCalendar trimToDays(GregorianCalendar arg) {
      GregorianCalendar result = arg;
      result.set(Calendar.HOUR_OF_DAY,0);
      result.set(Calendar.MINUTE, 0);
      result.set(Calendar.SECOND, 0);
      result.set(Calendar.MILLISECOND, 0);
      return result;
  }

グレゴリオ暦の適切な値をゼロに設定することで、トリムを強制することに注意してください。また、アメリカンスタイルの数値引数で動作するコンストラクターも提供します。これらは本のコードでよく使用するため、簡単にすることができます...

ここに、比較操作、基本オブジェクトに委譲する動作の例を示します。

class MfDate...

  public boolean after (MfDate arg) {
    return getTime().after(arg.getTime());
  }
  public boolean before (MfDate arg) {
    return getTime().before(arg.getTime());
  }
  public int compareTo(Object arg) {
    MfDate other = (MfDate) arg;
    return getTime().compareTo(other.getTime());
  }
  public boolean equals(Object arg) {
    if (! (arg instanceof MfDate)) return false;
    MfDate other = (MfDate) arg;
    return (_base.equals(other._base));
  }
    public Date getTime() {
        return _base.getTime();
    }

ラッパーを使用すると、便利だと思うが、基本クラスにはない動作を追加することもできます。

class MfDate...

  public MfDate addDays(int arg) {
    return new MfDate(new GregorianCalendar(getYear(), getMonth(), getDayOfMonth() + arg));
  }
    public MfDate minusDays(int arg) {
        return addDays(-arg);
    }