2023/12/12

DXL や JSON の日付値の変換

連載:DXL Step-by-Step では DXL(Domino XML Language)、連載:つないでみよう では JSON が頻繁に登場します。DXL(XML)も JSON もデータ構造を表す言語で、それぞれ標準化されていますが、日付/時刻値については共通の仕様になっています。

以下の画像は DXL と JSON 内の日付/時刻値の例です。


DXL 20230913T110634,71+09
気象庁 JSON 2023-12-01T11:00:00+09:00

この表記は ISO 8601 として標準化されています。この仕様について整理しつつ、変換プログラムを LotusScript で作成してみましょう。


ISO 8601 の仕様

ISO 8601 は、日付と時刻、タイムゾーンを文字列で表す仕様です。基本的には、日付を年 4 桁、月と日を 2 桁で表し、時刻の時分秒をそれぞれ 2 桁で表します。日時を表す各要素は、大から小で固定されており、月日年などの順序は認められていません。

表記方法として、基本形式と拡張形式が定義されています。基本形式は各要素を区切り文字なしで、拡張形式が区切り文字ありの書式と覚えればよいと思います。上記事例では、DXL が基本形式、JSON が拡張形式となります。


◇ 日付値

日付値は年月日の順で、年 4 桁、月と日を 2 桁で表します。

基本形式では 19970713 と 8 桁の連続した数値で表します。拡張形式は 1999-07-13 となり、区切り文字に "-" を使用します。"/" など他の区切り文字は認められていません。

なお、仕様としては、日付を省略し年月を表現したり、その年の何日目というような特殊な表現も定義されています。


◇ 時刻値

時刻値は、時分秒をそれぞれ 2 桁で表します。基本形式では T060328、拡張形式は T06:03:28 のようになります。区切り文字は ":" を使用します。

時刻の前には必ず "T" の文字が付加されます。また、時刻は 24 時制のみサポートしており、12 時制の定義はありません。

時刻値は、少数で表現できる仕様となっています(拡張形式の場合は最後の桁のみ)。T060328.50 のようにミリ秒を表すことができます。また、T06:03.12 で秒以下を少数表記したり、T06.123 で分以下を少数表記できるなど特殊な表記も定義されています。

なお、小数点は日本で使用されている "."(ピリオド)以外に ","(カンマ)も利用できます。上記 DXL の例ではカンマになっていますね。


◇ タイムゾーン

時刻の後ろに "Z" を指定すると、協定世界時(UTC)を表します。UTC より進んでいる時間帯の場合は "+"、遅れている場合は "-" を付加し、時差を 2 桁、もしくは 4 桁の数値で記述します。

上位2桁が時間を表します。4 桁の指定は一部地域で採用されている分単位の時差で必要となります。拡張形式での時間と分の区切り文字は ":" を使用します。


サンプルプログラム

ISO 8601 の日付/時刻値(文字列)をノーツで使用できる日付/時刻値(Variant 型)に変換する関数を作成してみました。

機能的には次の通りです。

  • 日付のみ、時刻のみ、日付/時刻値に対応
  • 標準形式と拡張形式に対応
  • 時刻はノーツのタイムゾーンに合わせて時差を換算

また、機能的に省略した点は次の通りです。

  • 日付の省略や時刻の少数表記などの特殊な指定
  • ミリ秒の計算(Variant 型の日付/時刻値で取り扱えないため)
  • 分単位の時差(NotesDateTime の TimeZone プロパティがサポートしていないため)

引数に ISO 8601 の日付/時刻値(文字列)を指定し、戻り値が日付/時刻値(Variant 型)の関数となっています。

Public Function ISO8601ToDateTime(ByVal vsString As String) As Variant
   Dim vTmp As Variant
   Dim vReturn As Variant

   If InStr(vsString, "T") = 0 Then
      '日付のみ
      vReturn = xISO8601ToDate(vsString)
   Else
      '時刻を含む
      If Left(vsString, 1) = "T" Then
         '時刻のみ
         vReturn = xISO8601ToTime(Replace(vsString, "T", ""))

         'オバーフロー対応
         If vReturn < 0 Then
            '時差計算で前日(負の値)になった場合、24時間を加算
            vReturn = vReturn + 1
         ElseIf vReturn >= 1 Then
            '時差計算で翌日になった場合、24時間を減算
            vReturn = vReturn - 1
         End If
      Else
         '日付/時刻値
         '時刻部/日付部を準備

         vTmp = Split(vsString, "T")
         vReturn = xISO8601ToDate(vTmp(0))
         vReturn = vReturn + xISO8601ToTime(vTmp(1))
      End If
   End If

   ISO8601ToDateTime = vReturn
End Function

ISO 8601 の文字列が時刻のみ指定されている場合、時差の計算を行った結果、前日になったり、翌日になったりオーバーフローする場合の調整を行っています。時刻のみを扱う事例に出くわしたことがないため、このインプリが正しいのかは判断できていません。予めご了承ください。


日付部、時刻部の変換はそれぞれサブ関数化しています。まず、日付値に変換する関数は次の通りです。

Function xISO8601ToDate(ByVal vsDate As String) As Variant
   Dim sTmp As String
   Dim iY As Integer
   Dim iM As Integer
   Dim iD As Integer

   '区切り文字を消去
   sTmp = Replace(vsDate, "-", "")

   '日付値に変換
   iY = CInt(Left(sTmp,4))
   iM = CInt(Mid(sTmp, 5, 2))
   iD = CInt(Right(sTmp, 2))
   xISO8601ToDate = DateNumber(iY, iM, iD)
End Function

続いて、時刻部の変換関数です。

Function xISO8601ToTime(ByVal vsTime As String) As Variant
   Dim sTime As String
   Dim vTmp As Variant
   Dim iH As Integer
   Dim iM As Integer
   Dim iSec As Integer

   Dim iTZ As Integer
   Dim bTZ As Boolean

   bTZ = True
   If InStr(vsTime, "Z") > 0 Then
      'UTC
      iTZ = 0
      sTime = Replace(vsTime, "Z", "")
   ElseIf InStr(vsTime, "+") > 0 Then
      'UTC より進んでいる
      vTmp = Split(vsTime, "+")
      iTZ = CInt(Left(vTmp(1), 2))
      sTime = vTmp(0)
   ElseIf InStr(vsTime, "-") > 0 Then
      'UTC より遅れている
      vTmp = Split(vsTime, "-")
      iTZ = - CInt(Left(vTmp(1), 2))
      sTime = vTmp(0)
   Else
      'タイムゾーン指定なし
      sTime = vsTime
      bTZ = False
   End If

   '時差計算
   If bTZ = True Then
      Dim ndt As New NotesDateTime(Now)
      iTZ = -(iTZ + ndt.TimeZone)
   End If

   '少数を削除
   vTmp = Split(sTime, ",")
   sTime = vTmp(0)
   vTmp = Split(sTime, ".")
   sTime = vTmp(0)

   '区切り文字を削除
   sTime = Replace(sTime, ":", "")

   '時刻値に変換
   iH = CInt(Left(sTime,2)) + iTZ
   iM = CInt(Mid(sTime, 3, 2))
   iSec = CInt(Right(sTime, 2))
   xISO8601ToTime = TimeNumber(iH, iM, iSec)
End Function

なお、日本の時差は UTC より 9 時間早いですが、LotusSript の TimeZone プロパティは -9、 ISO 8601 では +09 となり、± が逆転している点に注意が必要です。


0 件のコメント:

コメントを投稿