読者です 読者をやめる 読者になる 読者になる

NTPからJSON形式で現在時刻を取得するサンプル


JSONP 形式での時刻配信

NICT日本標準時プロジェクトにて NTP の時刻情報を JSON で、こんな感じで取得できます。

jsont( { "id": "ntp-a1.nict.go.jp",
         "it": 1232963971.248,
         "st": 1232963971.920,
         "leap": 33,
         "next": 1230768000 } )

取得項目は以下の通りです

  • id:サーバID
  • it:発信時刻(クライアントから送信された時刻)
  • st:サーバ時刻
  • leap:next以前の時点での UTC と TAI の差(秒)
  • next: 次、または最後のうるう秒イベント時刻

Scala の Source.fromURL

Scala の Source.fromURL で JSON で取得した時刻情報を表示してみます。
JSON のパースは scala.util.parsing.json.JSON でできます。

import io.Source
import scala.util.parsing.json.JSON
import java.util.Date

object Main extends Application {
  val src = Source.fromURL("http://ntp-b1.nict.go.jp/cgi-bin/jsont","UTF-8").getLines.mkString
  val json = src.replace("jsont(", "").replace(")", "")
  for (tpl <- JSON.parse(json).get) {
    tpl match {
      case (t1:String, t2:Double) if(t1 == "st") => println(new Date((t2*1000).toLong))
      case _ =>
    }
  }
}

実行結果はこんな感じになります。簡単です。。

Fri Mar 25 05:49:08 JST 2011

Ntpクラスにマップする

少し手を入れて、以下のような Ntp クラスにマップしてみます。

class Ntp(id: String, it: Double, st: Double, leap: Double, next: Double)


最初に Ntp クラスに toString をオーバーライドして実装しておきます。

class Ntp(id: String, it: Double, st: Double, leap: Double, next: Double) {
  private def toDate(posixSec: Double) = new Date((posixSec * 1000).toLong)
  override def toString = "Local Time:" + toDate(it) + "\nNTP Time  :" + toDate(st)
}

JSON 形式で取得する時刻は UNIX Time(POSIX仕様) で、少数以下3桁(ミリ秒まで)なので、1000で割って桁数を合わせて日付に変換する函数も定義しておきます。


次に json を取ってくる処理を関数に切り出しておきます。

def url = "http://ntp-b1.nict.go.jp/cgi-bin/jsont?" + System.currentTimeMillis / 1000
def jsont = Source.fromURL(url, "UTF-8").getLines.mkString.replace("jsont(", "").replace(")", "")

URL にクエリとしてローカルの時刻を付けると NTP サーバの時刻とのずれが得られます。


取得した JSON をパースする ntpOf 関数を作成します。

def ntpOf(json: String):Option[Ntp] = JSON.parseFull(json) match {
  case Some(s) => mapNtp(s.asInstanceOf[Map[String, Any]])
  case None => None
}

JSON.parseFull にて JSON をパースすると、Option の結果が得られ、Option[Map[String, Any]]型となります(対象の JSON の内容によりますが、今回はこの形)。asInstanceOf でキャストして mapNtp 関数に渡します。Scala でのキャストは asInstanceOf にて行い、isInstanceOf にて型の判定ができます。


mapNtp 関数にて各マップのキーから値を取得して束縛します。

def mapNtp(map: Map[String, Any]) = for {
  id <- map.get("id")
  it <- map.get("it")
  st <- map.get("st")
  leap <- map.get("leap")
  next <- map.get("next")
} yield new Ntp(id.toString, asDouble(it), asDouble(st), asDouble(leap), asDouble(next))
def asDouble(d: Any) = d.asInstanceOf[Double]

マップの値は、今回のJSONの例だと、文字列と Double 値なので、Any型からキャストして Ntp オブジェクトを作成します。


最後に Ntp を画面出力する処理を。

def printNtp(ntp: Option[Ntp]):Unit = ntp match {
  case Some(s) => println(s)
  case None => println("none")
}

副作用のための関数なので戻り値なしの :Unit を明示します。


では。

printNtp(ntpOf(jsont))

とすると以下のような出力が得られます。

Local Time:Sat Mar 26 05:46:37 JST 2011
NTP Time  :Sat Mar 26 05:46:39 JST 2011

ソース

import io.Source
import scala.util.parsing.json.JSON
import java.util.Date

object Main {

  class Ntp(id: String, it: Double, st: Double, leap: Double, next: Double) {
    private def toDate(posixSec: Double) = new Date((posixSec * 1000).toLong)
    override def toString = "Local Time:" + toDate(it) + "\nNTP Time  :" + toDate(st)
  }

  def url = "http://ntp-b1.nict.go.jp/cgi-bin/jsont?" + System.currentTimeMillis / 1000
  def jsont = Source.fromURL(url, "UTF-8").getLines.mkString.replace("jsont(", "").replace(")", "")

  def ntpOf(json: String):Option[Ntp] = JSON.parseFull(json) match {
    case Some(s) => mapNtp(s.asInstanceOf[Map[String, Any]])
    case None => None
  }

  def mapNtp(map: Map[String, Any]) = for {
    id <- map.get("id")
    it <- map.get("it")
    st <- map.get("st")
    leap <- map.get("leap")
    next <- map.get("next")
  } yield new Ntp(id.toString, asDouble(it), asDouble(st), asDouble(leap), asDouble(next))
  def asDouble(d: Any) = d.asInstanceOf[Double]

  def printNtp(ntp: Option[Ntp]):Unit = ntp match {
      case Some(s) => println(s)
      case None => println("none")
  }

  def main(args: Array[String]): Unit = {
    printNtp(ntpOf(jsont))
  }
}

それだけです。。