Takenoff Labs

Lotus Notes/Domino に関する Tips や、クラシックの名曲などを紹介します

[Notes/Domino] 設計解析講座: TIMEDATE 構造体

TIMEDATE 構造体については、この連載では解説しないつもりだったのですが、やっぱり解説しておくことにします。これを理解しておくと、いろいろと便利だと思いますので。

構造体の定義

TIMEDATE 構造体は、以下のように定義されています。

typedef struct tagTIMEDATE {
   DWORD Innards[2];
} TIMEDATE;

DWORD は4バイトの変数ですから、単純に4バイトの変数が2つくっついているだけです。

これを LotusScript で宣言すると、以下のようになります。

Type TIMEDATE
    Innards(1) As long
End Type

場合によっては、構造体を定義せずに、8バイトの変数(Double、Currency)を使うこともできます。(4バイトずつにバラすのが若干面倒ですが。)

ここまでは簡単ですね。

TIMEDATE の値とレプリカID

では次に、データの中身を見てみましょう。

「2010/11/23 21:55:35」という日時値が入っているフィールドをバイナリダンプしてみると、以下のようになります。(※バイナリダンプするツール(管理人作)については、年末~来年早々に公開予定です。)

D5 01 47 00 E4 77 25 49

バイトオーダーはリトルエンディアンです。したがって、実際の16進値は以下のようになります。

492577E4004701D5

この値を見て気付くことはありませんか? そうレプリカIDですね。レプリカIDとは、実はそのDBが作成されたときの日時値(TIMEDATE)なのです。

これは UNID でも同じで、UNID は TIMEDATE が2つ重なったものです。1つ目の TIMEDATE はランダムな値、2つ目の TIMEDATE は作成日時を表します。

0F86C6A1:78995762-492577D4:004C6D3F
└  ランダム値 ┘ └   作成日時  ┘ 

ちなみに、TIMEDATE を上述した LotusScript の構造体にセットすると、以下のようになります。

Innards(0) = 0x004701D5
Innards(1) = 0x492577E4

4バイトの変数が2つなので、上記のようになることに注意してください。
この場合、Innards(0) が時刻値で、Innards(1) が日付値です。

日付のみ、時刻のみの場合

TIMEDATE で注意すべきなのは、日付のみ、時刻のみの値があるということです。日付のみの場合は、Innards(0) が 0xFFFFFFFF、時刻のみの場合は、Innards(1) が 0xFFFFFFFF になります。(ワイルドカードの場合は両方とも 0xFFFFFFFF になります。)

日付のみ、または時刻のみの場合は、現地時刻がそのままセットされますが、日付・時刻の両方がセットされる場合は、タイムゾーン情報+グリニッジ標準時がセットされます。この点、特に注意してください。

時刻値

時刻値は100分の1秒単位で値を持っています。ノーツクライアントからは秒までしか持っていないように見えますが、実は100分の1秒単位でデータを持っているのです。

たとえば例の「0x004701D5」=「21:55:35」(=グリニッジ標準時で「12:55:35」)の場合、以下のように構成されています。

  12時 * 60 * 60 * 100 = 4,320,000
+ 55分 * 60      * 100 =   330,000
+ 35秒           * 100 =     3,500
+ 25(100分の1秒)       =        25
----------------------------------
                         4,653,525 = 0x004701D5

時刻値は比較的簡単ですね。

日付値

日付値はちょっとややこしいです。例の「0x492577E4」をビット値に直して解説します。

0 1 00 1001 0010 0101 0111 0111 1110 0100
A B C   D                 E 
  • A = サマータイム(DST)実施地域の場合 1、それ以外は 0
  • B = グリニッジ標準時より東の場合は 1、西の場合は 0
  • C = グリニッジとの時差(分)(0 = 0分、1 = 15分、2 = 30分、3 = 45分)
  • D = グリニッジとの時差(時)(これは数値がそのまま時間となる)
  • E = 紀元前4713年1月1日からの経過日数

最初の1バイト(= 8 ビット)にタイムゾーン情報が入っていて、残りが経過日数というわけです。経過日数の基準日の、紀元前4713年1月1日という値がどこから出てきたのかよくわかりませんが、とにかく、LotusScriptの基準日1900年1月1日とは異なる点に注意してください。

上記を見ると、日本で作成されたレプリカIDが「4925~」で始まる理由がよくわかります。「0x49」はタイムゾーンの値ですし、「0x25」は紀元前4713年1月1日からの経過日数があまりにも大きいので、数十年レベルでそこの値が変わることはないのです。

変換関数

TIMEDATE の値を文字列に変換するのは簡単です。ConvertTIMEDATEToText を使って以下のような関数を用意するとよいでしょう。

Type TIMEDATE
    Innards(1) As long
End Type

Public Const NOTESDLL = "nnotes.dll"
Declare Function ConvertTIMEDATEToText Lib NOTESDLL (ByVal IntlFormat As Integer, _
ByVal TimeFormat As Integer, InputTime As TIMEDATE, ByVal retTextBuffer As String, _
ByVal TextBufferLength As Integer, retTextLength As Integer) As Integer

Function APITIMEDATE2String(td As TIMEDATE) As String
    Dim s As String * 81
    Dim i As Integer
    
    Call ConvertTIMEDATEToText(0, 0, td, s, 80, i)
    APITIMEDATE2String = StrLeft(s & Chr(0), Chr(0))
End Function

これだけだと芸がないので、TIMEDATE と LotusScript の日時値を相互変換する関数を書いてみました(おもいっきり日本仕様です)。まぁ、TIMEDATE → Variant の場合は、ConvertTIMEDATEToText で返った戻り値を CDat すればいいだけの話ですが、こういうふうにも書けるというのを見てみてください。カスタマイズすれば、いろいろな処理が書けると思います。

Type TIMEDATE
    Innards(1) As long
End Type

Public Sub APITIMEDATE2Variant(td As TIMEDATE, retDt As variant)
    Dim dblDate As Double
    Dim dblTime As Double
    Dim lngTime As Long
    Dim lngHour As Long
    Dim lngMin  As Long
    Dim lngSec  As Long
    
    lngTime = td.Innards(0) \ 100
    lngHour = lngTime \ 3600
    lngMin  = (lngTime - (lngHour * 3600)) \ 60
    lngSec  = lngTime - (lngHour * 3600) - (lngMin * 60)
    
    dblDate = CDbl((td.Innards(1) And &H00FFFFFF&) - 2415019)
    dblTime = CDbl(TimeNumber(lngHour, lngMin, lngSec))
    
    If td.Innards(0) = &HFFFFFFFF& Then
        retDt = CDat(dblDate)
    ElseIf td.Innards(1) = &HFFFFFFFF& Then
        retDt = CDat(dblTime)
    Else
        retDt = CDat(dblDate + dblTime)
        retDt = retDt + TimeNumber(9,0,0) '日本仕様(時差+9h)
    End If
End Sub

Public Sub APIVariant2TIMEDATE(ByVal dt As Variant, retTD As TIMEDATE)
    Dim lngDate As Long
    
    If retTD.Innards(0) <> &HFFFFFFFF& And retTD.Innards(1) <> &HFFFFFFFF& Then
        dt = dt - TimeNumber(9,0,0) '日本仕様(グリニッジ標準時にする)
    End If
    
    If retTD.Innards(0) <> &HFFFFFFFF& Then
        retTD.Innards(0) = (Hour(dt) * 3600 + Minute(dt) * 60 + Second(dt)) * 100
    End If
    
    If retTD.Innards(1) <> &HFFFFFFFF& Then
        lngDate = CLng(DateNumber(Year(dt), Month(dt), Day(dt)))
        If retTD.Innards(0) <> &HFFFFFFFF& Then
            lngDate = lngDate Or &H49000000& '日本仕様(タイムゾーン情報)
        End If
        retTD.Innards(1) = (lngDate + 2415019)
    End If
End Sub

「2415019」という数字は、紀元前4713年1月1日~紀元後1900年1月1日の日数です。この値を使うと、日付値の相互変換ができます。

上記サンプルは、現地時刻ではなく日本時刻に無理やり合わせています。現地時刻を取得したい場合は、タイムゾーン情報から時差などを取得する必要があるので、ちょっと面倒です。おとなしく API 関数を使っておいたほうが楽ですね。

1 Star2 Stars3 Stars4 Stars5 Stars (No Ratings Yet)
読み込み中...

トラックバック

トラックバックはありません

コメント

コメントはありません

※コメントは承認制となっております。管理者が承認するまで表示されません。申し訳ありませんが、投稿が表示されるまでしばらくお待ちください。





(以下のタグが使えます)
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

For spam filtering purposes, please copy the number 4408 to the field below:

^
×