はじめに
こんにちは!
ゆっくり実況者のだいちまるです!
早速ですが、みなさんこんなこと思ったことはありませんか?
_人人人人人人人人人人人人人人_
> クソ楽に時刻同期したい! <
 ̄Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y ̄
まぁ時刻同期でなくても取得とかでもいいです
要するに(?)クライアントサイドのJavaScriptから正確な時刻を取得したりするのって、APIとかないと難しいよね?!
特定のAPIに頼るのも不安だし誰でも使えるようプロトコル作っちゃおう!ってお話です。
この記事に書いてあるコードはご自由に使っていただいて構いません!
完成物 (EpochLink)
作るまでの流れより先に完成物だけおいておきます!
JSONを使用して時刻を取得できるプロトコルです。(誰でも作れるような簡単仕様)
HTTP/HTTPS通信で取得できるのでJavaScriptからも簡単に使用できます!(なんならこれのためだけにライブラリも作りました)
EpochLink
取得できるデータはこんな感じ↓
{
"protocol": "EpochLink",
"version": "1.0.0",
"address": "https://elp.emptybox.win/",
"time_zone": "UTC",
"iso8601_time": "2024-05-22T17:01:35+00:00",
"unix_time_ms": 1716397295633,
"unix_time": 1716397295
}
仕様
EpochLinkはこんな仕様になっています。
{
"protocol": "EpochLink",
"version": "1.0.0",
"address": "https://elp.emptybox.win/",
"time_zone": "UTC",
"iso8601_time": "2024-05-22T17:01:35+00:00",
"unix_time_ms": 1716397295633,
"unix_time": 1716397295
}
Name | Data | Type |
---|---|---|
protocol | プロトコル名 | 文字列/String |
version | プロトコルのバージョン | 文字列/String |
address | サーバーのアドレス | 文字列/String |
time_zone | 時刻のタイムゾーン | 文字列/String |
iso8601_time | ISO8601に準拠した時刻 | 文字列/String |
unix_time_ms | ミリ秒を含むunix時刻 | 数値型/int |
unix_time | ミリ秒を含まないunix時刻 | 数値型/int |
時刻同期(取得)のしくみ
EpochLinkではサーバーはISO8601に対応した時刻とUNIX時刻(ミリ秒もあるよ)を配信します!
で、クライアント側がサーバーにリクエストを送ってJSONをもらいます。
もらったJSONの中にUNIX時刻があるのでそれを元にその地域ごとの形式に変換して現在時刻として扱うわけです。
もちろん少しながらズレはありますけどね...
EpochLinkでは、現状NTPなどにある階層構造はありません。
また、サーバーも多分一つしかありません。(どなたでも構築いただけます)
時刻補正のしくみ
時刻をやりとりするにあたって、やはりタイムラグなどで時刻のずれはでてしまいます!
なので、EpochLinkでは時刻補正機能をクライアント側で実装することをおすすめしています。
サンプルコードとJSライブラリはすでに時刻補正機能実装済みです。
もし、めちゃくちゃ超高精度な時刻補正機能を使いたいのなら、NTPなどのより正確性に優れたプロトコルを使ってください!
で、仕組みについてですが、こんな仕組みになっています(クライアントサイド)
1. EpochLinkサーバーにリクエストする直前の時刻を記録
2. EpochLinkからのレスポンスが返ってきた時刻を記録
3. 2と3の差を求め、2で割る(往復分あるため2で割る)
4. EpochLinkサーバーのレスポンスに含まれている時刻に3で求めた差を足す
もちろん正確性を保証するものではありませんが、これである程度の補正を行うことはできます!
セキュリティ
現状EpochLink側で実装しているセキュリティ対策はありません。
完全にHTTPSにお任せしています。(将来的にはセキュリティ対策するかも?)
サーバーの実装
サーバーの実装です。
サーバーの時計が狂っていると配信する時刻も狂ってしまいます!
サーバーの時計を定期的にあわせる必要がありますね
Node.js
var port = 8080
var http = require('http');
var server = http.createServer(function(request, response){
response.writeHead(200,{'Content-Type': 'application/json; charset=utf-8'},{'Access-Control-Allow-Origin': '*'});
var now = new Date();
time_json = {
"protocol": "EpochLink",
"version": "1.0.0",
"address": request.protocol + '://' + request.headers.host + request.url,
"time_zone": "UTC",
"iso8601_time": now.toISOString(),
"unix_time_ms": now.getTime(),
"unix_time": Math.floor(now.getTime() / 1000)
}
response.end(JSON.stringify(time_json));
})
server.listen(port);
console.log("Serving on port "+port+"...");
php
<?php
header("Content-Type: application/json");
header("charset=utf-8");
header("Access-Control-Allow-Origin: *");
date_default_timezone_set('UTC');//Set UTC
$date = new DateTime();
$response = [
"protocol" => "EpochLink",
"version" => "1.0.0",
"address" => (empty($_SERVER['HTTPS']) ? 'http://' : 'https://') . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'],
"time_zone" => date_default_timezone_get(),
"iso8601_time" => $date->format(DateTime::ATOM),
"unix_time_ms" => $date->getTimestamp() * 1000 + round($date->format('u') / 1000),
"unix_time" => (int)$date->format('U')
];
echo json_encode($response, JSON_UNESCAPED_UNICODE);
?>
Python
from wsgiref.simple_server import make_server
import json
import datetime
import time
port = 8080
def timejson(port):
now = datetime.datetime.now(datetime.timezone.utc)#UTC Date
timedata = {
"protocol": "EpochLink",
"version": "1.0.0",
"address": "http://localhost:"+str(port)+"/",
"time_zone": "UTC",
"iso8601_time": now.isoformat(timespec='seconds'),
"unix_time_ms": int(time.time() * 1000),
"unix_time": int(time.time())
}
return timedata
def app(environ, start_response):
status = '200 OK'
headers = [
('Content-type', 'application/json; charset=utf-8'),
('Access-Control-Allow-Origin', '*'),
]
start_response(status, headers)
return [json.dumps(timejson(port)).encode("utf-8")]
with make_server('', port, app) as httpd:
print("Serving on port "+str(port)+"...")
httpd.serve_forever()
クライアントの実装
クライアントの実装です。
JSONをパースして時刻を取り出し、補正するだけなので、とても簡単な処理になります!
JavaScriptのは少し長いのでここには書きません。(EpochLinkのEpochLink.jsってやつです。サンプルコードにも含まれてます)
php
<?php
date_default_timezone_set('UTC');//Set UTC
$start_get_dt = (int)substr(str_replace('.', '', microtime(true)),0,13);//Get Start Time
$url = "https://elp.example.com/";//EpochLinkServer Address
$json = file_get_contents($url);
$end_get_dt = (int)substr(str_replace('.', '', microtime(true)),0,13);//Get End Time
$data = json_decode($json, true);
$now_unix_ms = $data["unix_time_ms"] + (($end_get_dt - $start_get_dt) / 2);
echo date('Y/m/d H:i:s', (int)substr($now_unix_ms,0,10));
?>
Python
import time
import requests
url = "https://elp.example.com/"#EpochLinkServer Address
start_get_dt = int(time.time() * 1000)
response = requests.get(url)
json_data = response.json()
end_get_dt = int(time.time() * 1000)
now_unix_ms = json_data["unix_time_ms"] + ((end_get_dt - start_get_dt) / 2)
print(time.strftime('%Y/%m/%d %H:%M:%S', time.gmtime(now_unix_ms // 1000)))
おわりに
いかがでしたか?
超簡単なこの時刻同期プロトコルは、完全に自己満足の範囲で作成したものですが、NTPのstratum 1サーバーへの負荷を減らしたり、NTPが使用できない環境で簡単に時刻同期をするのに使用できると考えています。
正確なWeb時計サイトを作成したりするのにも使用できます
また、まだありませんが、複数のサーバーから時刻を取得して平均値を出したりすれば、より正確な時刻を求めることもできますし、様々な使い方があると思います!
もし興味を持っていただけたらこちらのサンプルコードやサーバーについてコードを配布しているサイトもご確認いただけると嬉しいです!
どなたでも自由に使っていただけるサーバーも公開しています。
EpochLink
また、このサイトで配布しているコードはすべてNYSLライセンスで、どなたでもEpochLinkサーバーを立てていただくことも可能です。
最後までお読みいただきありがとうございました!
EpochLinkについて改善点などありましたら是非コメントなどしていただけると幸いです!
それではまた!