foobar2000の日付時刻データをDate型の値に変換する

foobar2000の日付時刻データをDate型の値に変換する

Date型の値を、foobar2000独自の日付時刻文字列に変換するのは

akashi-keirin.hatenablog.com

で作成済み。

今度は、その逆をやってみる。

考え方

foobar2000のPlayback Statisticsでは、再生日時を18ケタの数字で表現している。

そのうち、整数秒までの表現で良いのなら、下7ケタは無視してもよい。

上11ケタが秒単位で表された日時である。

そして、たとえば2012年1月1日 0:00:00が12969817200であることはわかっている。

したがって、foobar2000独自の日付時刻文字列(めんどくさいので、以下「日時コード」という。)を求めるには、

  1. 当該の日時コードと基準日の日時コードとの差を求める
  2. 上記1.を8640024 * 60 * 60)で割る
  3. 上記2.で得た数が基準日からの日数差なので、基準日に加算する
  4. 上記3.で得た値が年月日
  5. 上記2.で得た余りを3600(60 * 60)で割る
  6. 上記5.で得た商が時
  7. 上記5.で得た余りを60で割る
  8. 上記7.で得た商が分
  9. 上記7.で得た余りが秒

まあ、比較的簡単に求めることができる。

必要な値の特定

日時コードは最大3種類(「FirstPlayed」「LastPlayed」「Added」)あり、それぞれの要素が必ず書き込まれているとは限らないので、XMLの要素の中のどの部分が対象の日時コードであるのかは一意に決まっていない。

そこで、XML要素の中から対象の日時コードを引っ張ってくるFunctionが必要である。ただ、幸い日時コードは18文字であることが決まっているので、対象の日時コードの先頭位置さえわかれば、抜き出すことはわけもなくできる。

コーディング

……というわけで、コーディング。

まずは、XML要素の中から、対象の日時コードを引っ張ってくるFunction。

リスト1 標準モジュール宣言セクション
'Constants'
Public Enum DateMode  '……(1)'
  [_dmStart] = 3
    dmFirstPlayed = [_dmStart]
    dmLastPlayed = dmFirstPlayed + 2
    dmAdded = dmLastPlayed + 2
End Enum

'2012年1月1日 0時00分00秒の日付時刻値の上11桁'
Private Const STANDARD_DATE_VALUE As Currency = 12969817200#
Private Const STANDARD_DATE As Date = "2012/1/1"
'一日あたりの秒数'
Private Const DAY_BY_SECONDS As Currency = 86400

(1)からの6行では、Enumの宣言をするのに、id:x1xy2xyz3 さんに教えてもらったテクニックを早速使ってみた。

メンバの値を「3」、「5」、「7」にしているのには一応意味があるんだが、今回は関係ないので説明は省く。

後半の定数群は、コメントで記したとおり。変換計算をするときに必要な値なので、定数化しているだけ。

スト2 標準モジュール

お次は対象の日時コードを抜き出すFunction。

Public Function getDateTimeCodeFromElement( _
            ByVal targetElement As String, _
            ByVal targetMode As DateMode) As String  '……(2)'
  Dim ret As String
  ret = ""
  Dim modeStr As String
  Select Case targetMode  '……(3)'
    Case dmFirstPlayed: modeStr = "FirstPlayed="""
    Case dmLastPlayed: modeStr = "LastPlayed="""
    Case dmAdded: modeStr = "Added="""
  End Select
  Dim startPos As Long
  startPos = InStr(1, targetElement, modeStr)  '……(4)'
  If startPos = 0 Then GoTo Finalizer  '……(5)'
  startPos = startPos + Len(modeStr)  '……(6)'
  ret = Mid(targetElement, startPos, 18)  '……(7)'
Finalizer:
  getDateTimeCodeFromElement = ret
End Function

まず(2)の

Public Function getDateTimeCodeFromElement( _
            ByVal targetElement As String, _
            ByVal targetMode As DateMode) As String

で引数設定。

第1引数targetElementで、XMLの要素文字列を受け取る。

ちなみに、XML要素文字列は、全ての内容が書き込まれていると

  <Entry ID="f1a6d9d835f110fa" Count="2" FirstPlayed="129945499660000000" LastPlayed="131972743430000000" Added="131907872960000000" />

こんなクソ長ったらしいものである。

第2引数は、リスト1で設定した列挙体DateMode型。どの日時コードを抜き出すのかを指定するのに使う。

(3)からの5行

Select Case targetMode
  Case dmFirstPlayed: modeStr = "FirstPlayed="""
  Case dmLastPlayed: modeStr = "LastPlayed="""
  Case dmAdded: modeStr = "Added="""
End Select

で、対象の日時コードを探すためのキーとなる文字列を変数targetModeにセットする。

FirstPlayed」だけでなく、「FirstPlayed="」までをキーにしているのには意味がある。後述する。

(4)の

startPos = InStr(1, targetElement, modeStr)

で、まず「FirstPlayed="」、「LastPlayed="」、「Added="」を検索し、XML要素の何文字目の位置に出てくるのかを割り出す。

この段階で変数startPosにぶち込まれるのは、たとえば「FirstPlayed="」の先頭の「F」の位置に過ぎない。

(5)の

If startPos = 0 Then GoTo Finalizer

はガード節。この段階でstartPos0だということは、対象の日時が設定されていないということなので、ここで処理を打ち切る。

(5)をくぐり抜けたということは、対象の日時が設定されているということになるので次へ進む。

(6)の

startPos = startPos + Len(modeStr)

で日時コードの先頭位置を割り出すことができる。

たとえば、「FirstPlayed」の場合、先頭の「F」が20文字目に出てくるとすると、「FirstPlayed="」が13文字あるので、startPosの値は「33」。

先頭から33文字目というのは、「FirstPlayed="」のすぐ次の位置なのである。

日時コードは18文字と決まっているので、あとは(7)の

ret = Mid(targetElement, startPos, 18)

で先頭位置から18文字分切り出してやればよい。

これで、日時コードを抜き出すことができる。

リスト3 標準モジュール

そして、日時コードをDate型の値に変換するFunction。

Public Function getDateTime( _
            ByVal dateCode As String) As Date
  Dim ret As Date
  '上11ケタを切り出す'
  Dim tmpCode As String
  tmpCode = Left(dateCode, 11)
  '基準日の値との差を求める'
  Dim tmpCurr As Currency
  tmpCurr = CCur(tmpCode)
  Dim dateDiff As Currency
  dateDiff = tmpCurr - STANDARD_DATE_VALUE
  '日数差を求める'
  Dim tmpDay As Currency
  tmpDay = dateDiff \ DAY_BY_SECONDS
  '日数差を元に日付を求める'
  Dim tmpDate As Date
  tmpDate = tmpDay + STANDARD_DATE
  ret = tmpDate
  '時刻を求める'
  Dim tmpTime As Currency
  tmpTime = dateDiff Mod DAY_BY_SECONDS
  Dim tmpHour As Long
  tmpHour = tmpTime \ 3600
  Dim tmpMinute As Long
  tmpMinute = (tmpTime Mod 3600) \ 60
  Dim tmpSecond As Long
  tmpSecond = ((tmpTime Mod 3600) Mod 60)
  '日付と時刻を合成する'
  ret = tmpDate + TimeSerial(tmpHour, tmpMinute, tmpSecond)
  getDateTime = ret
End Function

まあ、コード中のコメントを見たら、何をやっているのかはわかると思う。

使ってみる

上で挙げた

  <Entry ID="f1a6d9d835f110fa" Count="2" FirstPlayed="129945499660000000" LastPlayed="131972743430000000" Added="131907872960000000" />

FirstPlayedの日時コード「129945499660000000」がいつのことを表すのか、調べてみる。

ちなみに、このXMLの元になった曲データは

f:id:akashi_keirin:20190326170215j:plain

こいつのもの。

FirstPlayedは、「2012-10-13 06:12:46」である。

イミディエイト・ウインドウに、

?getDateTime(getDateTimeCodeFromElement("  <entry count="" added="" lastplayed="" firstplayed="" f1a6d9d835f110fa="" 131907872960000000="" 131972743430000000="" 129945499660000000="" 2="">",dmFirstPlayed))

と打ち込んで、[Enter]。

f:id:akashi_keirin:20190326170219j:plain

うむ。バッチリ。

おわりに

で、何に使うんでしょう???