Java/Groovyでうるう秒を扱う方法
Java言語でうるう秒を扱う方法を調べたのでまとめておきます。Twitterで質問したらたくさんのリプライ、エアリプがありそれらをもとに調べることで達成できました。ありがとうございました。
概要
- Java標準APIのOracle実装ではうるう秒を扱えない。Calendar, Dateなどの旧型のAPI、ZonedDateTimeなどのDate and Time APIどちらも。
- ThreeTen-Extra というライブラリを使うとうるう秒を扱える。
うるう秒を扱えるということに対して期待していること
4年に1度だけ2/29があるうるう年のように、2年から5年に1度くらいの間隔で世界中の時計が1秒だけ増えることがあります。これをうるう秒と言っています。最近だと日本時間で2017/1/1 08:59:59 のつぎが 2017/1/1 08:59:60 となりました。普段はない60秒があり、1分間が61秒になりました。 これはいつ挿入されるかは規則はいまのところありません。(どれくらい前に告知されるのかは決まっていないのかな?)
で、システムにおいてうるう秒においても 08:59:60 のように時刻を正確に記録できるのかが気になりました。
というのも例えば、UNIXTIMEはうるう秒を丸めるという仕様になっています。
具体的には次のような形になります。
- 東京の時刻
- UNIXTIME
- 2012-07-01T08:59:59+09:00[Asia/Tokyo]
- 1341100799
- 2012-07-01T08:59:60+09:00[Asia/Tokyo] <= うるう秒挿入
- 1341100799 <= 1秒前と同じ
- 2012-07-01T09:00:00+09:00[Asia/Tokyo]
- 1341100800
ということで、UNIXTIMEを使うとうるう秒時点の扱いを気をつけなければいけません。(保存にしろ、描画にしろ)
ので、流れとして
- 「2012-07-01T08:59:60+09:00[Asia/Tokyo]」という時刻を表現できるようにするにはどうしたらいいのだろうか?という視点で調べました。
- このシステムはうるう秒が来ても正確に動作するシステムであるといえるようにしたい。
- となると、うるう秒を発生させるテストを書かなければいけない。
- (例えば)Javaでうるう秒を任意に起こすことはできるのか?
という形です。
そこで、「2012-07-01T08:59:59+09:00[Asia/Tokyo]」のオブジェクトを生成し、1秒すすめたときに「2012-07-01T08:59:60+09:00[Asia/Tokyo]」が生成できるのか?ということを調べました。
Java標準のAPIだとどうなるのか
Java標準の時間を扱うクラスというとJava7まではjava.util.Calendar、java.util.Dateを使い、Java8からはDate and Time APIである java.time.ZonedDateTime などを使います。
JavaDocにはOS依存だよーとか、Java実装依存だよーとか書かれていたので、次のような環境で試しました。
ですがいずれの場合でも、「2012-07-01T08:59:59+09:00[Asia/Tokyo]」のオブジェクトを生成し、1秒すすめたときには 60秒にならず09:00:00になってしまいました。
Calendarの場合
def c = Calendar.getInstance(TimeZone.getTimeZone("Asia/Tokyo")) // 59秒を生成 c.set(1900 + 112, 6, 1, 8, 59, 59) // うるう秒にすすめる c.add(Calendar.SECOND, 1) // fail assert c.getTime().toString() == "Sun Jul 01 08:59:60 JST 2012" // うるう秒で生成 def d = new Date(112, 6, 1, 8, 59, 60) // fail assert d.toString() == "Sun Jul 01 08:59:60 JST 2012"
ZonedDateTimeの場合
// 59秒を生成 def zdt59 = ZonedDateTime.parse("2012-07-01T08:59:59+09:00[Asia/Tokyo]") // うるう秒にすすめる // fail assert zdt59.plusSeconds(1).toString() == "2012-07-01T08:59:60+09:00[Asia/Tokyo]"
ちなみにZonedDateTimeのparseメソッドはそもそも60秒をパースできませんし、Instantのparseメソッドは60秒をパースしても0秒に丸めてしまいます。
ThreeTen-Extraを使うとどうなるのか
ThreeTen-Extraというライブラリを使うとこれが実は出来ます!(なんだってー!なんのためのJSR310だっt
- Stephen Colebourneが「Joda-Time」をつくる
- JSR-310としてJoda-Timeのように便利にしようとDate and Time APIが取り込まれる
- Stephen ColebourneがJSR-310の拡張ライブラリ「ThreeTen-Extra」をつくる
もう、Stephen Colebourneに足向けて寝られないですね。
UtcInstantというクラスを使うとうるう秒を保持した計算ができるようになっています。なのでタイムゾーンなしの文字である「2012-06-30T23:59:59.000Z」を渡して1秒足してみて60秒になれば成功です。
import java.time.Duration import org.threeten.extra.scale.UtcInstant def ui = UtcInstant.parse("2012-06-30T23:59:59.000Z") def ui2 = ui.plus(Duration.ofSeconds(1) // success assert ui2.toString() == "2012-06-30T23:59:60Z"
ということでうるう秒を鑑みて時刻を考えるなら、UtcInstantとして常に計算し、日付、タイムゾーンは別で管理しておくという戦略をとればよさそうです。
ちなみにUtcInstantでは日付はMJD(修正ユリウス日)が使われています。のでだいたい5桁くらいで収まる範囲です。
ただしこれがバグっぽいAPIがある
UtcInstantにはisLeapSecond()というメソッドがあるのですが、これがうるう秒ちょうどになった瞬間はまだうるう秒じゃないという判定をしてしまっているように見えます。
- 08:59:59.999
- 08:59:60.000 <= うるう秒 <= ここでisLeapSecondがfalseになってしまう。
- 08:59:60.001 <= うるう秒 <= ここはtrueを返してくれる
- ...
- 09:00:00.000
まだThreeTen-Extraの仕様を読み切ってないのですが、仕様が僕のおもっているとおりならこれはtrueを返すべきところなので、まずかったらIssueをあげようかなーと。
そのほかの話
ここまでで、Javaでうるう秒を扱うという方法がわかったわけですが、システム全体で考えるといろいろと面倒です。 まず、Windowsはうるう秒をサポートしていません。
次に、Linuxではうるう秒については60秒になった瞬間に59秒をやり直します。(60秒から0秒のあいだまでもう一度59秒になります)
そして、NTPサーバがどのように返答するか次第でOSの挙動は変わります。
ちなみに、GoogleなんかではLeap Smearingといって、うるう秒の1秒を(うるう秒前後の)20時間かけてちょっとずつ時計の進みを遅くすることで60秒という1秒間を分散させています。こうすることで、NTPサーバに問い合わせても60秒という時間が指定されません。つまり、ある20時間だけほんの少しずつだけ時計が遅く進んでいるのです。
クラウドを使っている場合にはインフラのNTPまでを統一するのは難しいので、要件に応じてどこを何に統一するのか考える必要があります。
最後に
情報収集につきあってくださった id:megascus さん、エアリプしてくれた khasunuma さんありがとうございました。めっちゃ勉強になりました。 そしてコードを簡単に共有できるWandbox最高でした。Groovyの最新版うごかしてくれてありがとう。(もう少し言えば、@Grabが動くようになっているともっともっと嬉しいんだなー。)
参考
13 章 : Date and Time API · Java Study
- ここが正しかった。やはりJCP会員つよし。というか、このサイトかなりすごい。khasunumaさんすごい。
Java本格入門 ~モダンスタイルによる基礎からオブジェクト指向・実用ライブラリまで
- 作者: 谷本心,阪本雄一郎,岡田拓也,秋葉誠,村田賢一郎,アクロクエストテクノロジー株式会社
- 出版社/メーカー: 技術評論社
- 発売日: 2017/04/18
- メディア: 大型本
- この商品を含むブログを見る
- 作者: Joshua Bloch
- 出版社/メーカー: Addison-Wesley Professional
- 発売日: 2018/01/06
- メディア: ペーパーバック
- この商品を含むブログ (2件) を見る
Spockのレポート生成はHTML, Markdown, Asciidocできるし、カスタムも出来るんだぜ
GroovyのテスティングフレームワークであるSpockは標準ではレポート生成機能はありません。 多くはGradleでビルドしたときのxmlやhtmlを利用していると思います。
Spockにはspock-reportという拡張ライブラリがあり、これを依存関係に追加するだけでテスト結果のレポートを独自に追加で生成できます。 しかもなんと、HTMLだけではなくMarkdownやAsciidocとして生成することもできます。 また、最新のspock-reportではテストコードからレポートに文章を追加することも出来るようになりました。
今回はそんなspock-reportの便利機能をいくつか紹介します。
本ブログで説明しているサンプルコード全部入りのspockおよびspock-reportのプロジェクトテンプレートはこちら。
本記事はG* Advent Calendar 2017の16日目になります。
G* Advent Calendar 2017 - Qiita
- 最低限の使い方
- レポートをMarkdown/Asciidocとして生成する
- テストコードから説明を追加する
Spock1.1の新機能紹介
SpockというGroovy言語で記述するテスティングフレームワークがあります。 今回は2017/05/01にリリースされたバージョン1.1の新機能のうち3つを紹介します。
本記事はG* Advent Calendar 2017の14日目です。
- verifyAll メソッドによる(Soft Assert)
- where句のデータテーブルで左側の値を参照できる
- @PendingFeatureで未実装機能マーカーでテストを実行しない
- その他
GroovyのAST変換事情
Groovy言語ではAST変換をサポートしているのでその昔からAST変換をつかったライブラリがそこそこあります。 アノテーションでコード生成やボイラープレートなどの短縮、コード検査があります。
今回はAST変換の書き方というよりどういった成果物があったり、参考になる情報をまとめておきます。 本記事はG* Advent Calendarの12日目になります。
G* Advent Calendar 2017 - Qiita
- AST変換の方法
- AST変換の事例となる実装
爆速WebFW(らしい) Light-4JをGroovyから使ってみる
G* Advent Calendar 5日目の記事です。
JVM上で使えるWebAPIを開発するFWなどは多々ありまして、ハイパフォーマンスなものを探していたらどうやら Light-4Jというものがあるらしいと聞きつけました。 彼が言うにはそこそこ速いということで、ベンチマークも出しているんだぜと。
鵜呑みにはできないし、使い方とか知りたいので最小限のプロジェクトテンプレートをつくってみました。
JDK8が入っていれば、 ./gradlew run
で http://localhost:8081/baz で bazというテキストがレスポンスされると思います。
サンプルプロジェクトを移植しただけなので、Groovyらしさとかテストコードもありませんが。(ルーティング部分だけだったので)
Light4Jでは、Swagger 2.0, Open API 3.0, GraphQLに対応していたり、OAuth2.0に対応していたり、マイクロサービストレーサビリティ(X-Traceability-Id)に対応していたりとしていてよい感じです。(それぞれがライブラリで管理されているのもよい)
今回のプロジェクトテンプレートはそういったものは使っておらず、単純なAPIルーティング形式だけです。以下は使っている部分に関する知っていることの紹介です。
- Java向けを考えて開発されている
- 基本的な依存はundertow
- いろんなものがライブラリに切られている
- service.yml
- server.yml
- で、パフォーマンス
Java女子部でSpockの入門ハンズオン講師しました。 #javajo
Java女子部というコミュニティで2017/03/25に半日間のハンズオンでSpockというテスティングフレームワークの使い方を教えてきました。 Javaはだいたい書けるけど、Groovyとかよくわかりません!みたいな人に最低限のGroovyの使い方とSpockを教えるみたいな感じです。
すすめかたはだいたい次のような感じです。
- GitHubからソースコードをダウンロードしてもらう
- kyon_mmがスライドで概要をつたえる
- kyon_mmがIDEでサンプルコードをひらきながら解説する
- 参加者がサンプルコードのテストが失敗している部分を通るように直す
- kyon_mmがテストが通るようにコードを記述するのデモする
- Groovy/Spockの基本については2 - 5を繰り返す
- 解説がおわったらペアプロでTDDライクにSpockをつかいながらコードをかいてもらう
いわゆるKoan形式とか4clojureみたいなかんじといいますか、テストが通るように修正することで機能を学んでいくっていう感じです。
スライドに誤解をまねくような表現がないように配慮したつもりですが、もし違っていたらツッコミもらえるとうれしいです。 Groovyの解説はJava8のまま書いても動かないところを中心にしました。
また、当日サンプルコードがいくつか間違っていたので修正したものをGitHubにあげています。
正規表現のところに // によってエスケープがなんたらというテストを最初書いてしまっていて、 動かなかったのは、文字列としてであって正規表現でのテストではなかったためです。現在のGitHubのmasterでは文字列のテストに移してあります。すみませんでしたー><
全体的には自分の知識が再整理されたとか、TDDとはなんだーとか、自動テストの考えかたとかいろいろ興味をもってもらえたのがとても勉強になりました。 呼んでいただいたJava女子部の皆様には感謝です。特に声をかけていただいた、あやさんありがとう! Java女子部はもっと怖い人のいるところだとおもっていましたが、みなさん優しかったですし、楽しい人達でした。 参加者のみなさまからも楽しかったと言ってもらえたので、やってよかったなーっておもえました。
今後も関わるのであれば、Java女子部でF#をつっこむのは難しいでしょうし、GatlingとかBetamaxとかGebでしょうか。
GroovyとかSpockまぢべんりな子なので、ぜひ使ってみていただけると嬉しいです。 また、スライドやGitHubはご自由に使っていただいて構いません。みなさまでSpockを愛でる方向で。
Enjoy Spock!
テストをテストする方法-ミューテーションテスト- #gadvent
はじめに
これはG* Advent Calendarの12日目の記事です。今日はミューテーションテストについて書きます。明日はid:nobusue さんです。
概要
PITというツールの紹介です。「Javaプロダクトコードを機械的に変更してからテストを実行したときに、テストはそれを検知できるのか?」ということを調べてくれるツールで、SpockのテストやGradleからの実行に対応しています。
続きを読む