うさぎ組

ソフトウェア開発、チームによる製品開発、アジャイル、ソフトウェアテスト

Java/Groovyでうるう秒を扱う方法

Java言語でうるう秒を扱う方法を調べたのでまとめておきます。Twitterで質問したらたくさんのリプライ、エアリプがありそれらをもとに調べることで達成できました。ありがとうございました。

概要

  1. Java標準APIOracle実装ではうるう秒を扱えない。Calendar, Dateなどの旧型のAPI、ZonedDateTimeなどのDate and Time APIどちらも。
  2. 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を使うとうるう秒時点の扱いを気をつけなければいけません。(保存にしろ、描画にしろ)

ので、流れとして

  1. 「2012-07-01T08:59:60+09:00[Asia/Tokyo]」という時刻を表現できるようにするにはどうしたらいいのだろうか?という視点で調べました。
  2. このシステムはうるう秒が来ても正確に動作するシステムであるといえるようにしたい。
  3. となると、うるう秒を発生させるテストを書かなければいけない。
  4. (例えば)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

  1. Stephen Colebourneが「Joda-Time」をつくる
  2. JSR-310としてJoda-Timeのように便利にしようとDate and Time APIが取り込まれる
  3. 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が動くようになっているともっともっと嬉しいんだなー。)

参考

Java本格入門 ~モダンスタイルによる基礎からオブジェクト指向・実用ライブラリまで

Java本格入門 ~モダンスタイルによる基礎からオブジェクト指向・実用ライブラリまで

Effective Java (3rd Edition)

Effective Java (3rd Edition)

Scrumのカンファレンスに参加してきた #RSGT2018

Scrumのカンファレンス(ギャザリング)に参加してきました。

2018.scrumgatheringtokyo.org

なぜ参加しているのか、今回どんなイベントだったのか、僕にとって何がよかったのか。を書こうと思います。 日本でアジャイルに興味がある方ならぜひ参加してみるといいと思います。

  • なぜ参加しているのか
    • 自分がなぜRSGTで発表するのか
  • どんなイベントだったのか
  • 僕にとって何がよかったのか
    • 参加してよかった3つのこと
  • まとめ
続きを読む

ソフトウェアコードのはじめかた

  • プロジェクト名や名前空間の雑さはIDEなどの強力さを考慮して決めること
    • IntelliJ IDEAなど => 名前空間やファイル名変更が強力なので、雑に決めてもあとから変更できる
    • Vimのみ => 名前空間やファイル名変更が
  • プロジェクトをつくるときは次の手順でやること
  • 要件を確認してアーキテクチャと単語の整理
    • noteという名前空間をつくり、そこにメモ書き用テストファイルをつくる。
    • 要件をテストコードにはりつける
  • 要件の一部をテストコードに翻訳する。簡単なケースだけでいい。 : RED
  • プロダクトコードを仮実装する。 : GREEN
  • 他の入出力のケースをテストに追加する。三角測量。 : RED
  • プロダクトコードを修正する。 : GREEN
  • テストコードをリファクタリングする。 : GREEN
  • 何をクラスとして切り出すかは、何をつくりたいのかを定義している段階できまるので、コードをかきながら決めないほうがいい。
  • テストコードはできるだけ長く書きはじめていく。テストコードは動的構造を記述する部分であり、プロトコル定義ツールとして使う。
    • 小さいテストコードはあくまでデバッグのサポート、型システムのサポート程度に考えて記述する。
  • 静的構造はプロダクトコード側で記述できる内容なので、プロダクトコードのリファクタリングや大枠の設計でおこなうようにする。
  • テストコードとプロダクトコードの設計がかたまったら、noteにあるテストコードを適切な名前空間にきりだしてあげる。
    • ユースケースを確認するようなテストは userguide, デバッグサポートのテストは developerguideなどがわかりやすい。ただ、developerguideのほうはビルドツール、IDEなどの連携をかんがみて、プロダクトコードと同じ名前空間にしたほうが便利なときもある。

Scrumが難しいのは幻想-情熱の再定義- を講演してきました。 #RSGT2018

私が所属しているチームは2017年にいろんなプラクティスを実践してきました。その内容をRegional Scrum Gathering Tokyo 2018で発表しました。

2018.scrumgatheringtokyo.org

confengine.com

発表内容の概要

私達のチームは2016年までメトリクスの活用、スプリント期間の短縮、くじ引きで決めるPOやSM、などのプラクティスを通して改善を繰り返してきました。スクラムガイドもどんどん破りました。

このチームはScrumが難しいなんて思っていませんし、誰でも出来ると信じています。

チームが開発する製品は大きく変わりましたがScrumが難しいなんてことはありませんでしたし、

なによりこのチームのエッセンスを大学生40名に導入したところなんと1週間で1日スプリントをモノにしました。Scrumが難しいのは幻想だったのかもしれません。

我々のチームはこういったことを通して2017年にいくつかのプラクティスを確立しました。スプリント期間は1時間へ、チーム内ボトルネックへの対応時間は25分以内を保証、人的リソース活用の損益分岐点を常に意識できる開発プロセスです。

結果、1週間でレビューを35回以上、振り返りを30回以上行っています。1週間で改善した項目は最大で20アイテムにおよび、それらのムダ取りによって6ヶ月間で最大2倍の成果を生み出しています。

チームのパフォーマンスを最大化するために私達の計画的な学び方、偶発的事象からの学び方などをScrumの文脈でご紹介します。

スライド

speakerdeck.com

2017年の資料

Scrumありがとう、 そしてさようなら -Scrum 破- #rsgt2017 // Speaker Deck

2016年の資料

Scrum,Test,Metrics #sgt2016

Spockのレポート生成はHTML, Markdown, Asciidocできるし、カスタムも出来るんだぜ

GroovyのテスティングフレームワークであるSpockは標準ではレポート生成機能はありません。 多くはGradleでビルドしたときのxmlやhtmlを利用していると思います。

Spockにはspock-reportという拡張ライブラリがあり、これを依存関係に追加するだけでテスト結果のレポートを独自に追加で生成できます。 しかもなんと、HTMLだけではなくMarkdownやAsciidocとして生成することもできます。 また、最新のspock-reportではテストコードからレポートに文章を追加することも出来るようになりました。

今回はそんなspock-reportの便利機能をいくつか紹介します。

github.com

本ブログで説明しているサンプルコード全部入りのspockおよびspock-reportのプロジェクトテンプレートはこちら。

github.com

本記事はG* Advent Calendar 2017の16日目になります。

G* Advent Calendar 2017 - Qiita

  • 最低限の使い方
  • レポートをMarkdown/Asciidocとして生成する
  • テストコードから説明を追加する
続きを読む

Spock1.1の新機能紹介

SpockというGroovy言語で記述するテスティングフレームワークがあります。 今回は2017/05/01にリリースされたバージョン1.1の新機能のうち3つを紹介します。

Spock リリースノート

本記事は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変換の事例となる実装
続きを読む