White Box技術部

WEB開発のあれこれ(と何か)

Twitter2015年問題@2014年

※エラー原因が誤読出来る感じだったので本文直しました。ご指摘ありがとうございます。

新年あけましておめでとうございます。

お休みって素晴らしいですね。今年も頑張っていきましょう。

新年Rebuild

まさか元旦からRebuildが聞けるとは思ってもいなかったので、暇してる方の人間としてはありがたかったです。 で、その中で話題に上がった「年末に発生したAndroid端末でのTwitter強制ログアウト&ログイン出来ない問題」の原因だったらしい日付のフォーマットについて、実際に試してみました。

Rebuild: 73: It Is 2015 Already (N, naan)

Cだと%Y or %G、Javaだとyyyy or YYYY

ログインエラーの直接の原因は、Twitterのクライアント側の話ではなく、 サーバ側の返却する日付が「暦週の基準年」になっていたため、クライアント側の認証時のチェックでこけてたようです。

で、ここで言うところの暦週の基準年というものの動作確認のために、SimpleDateFormatを使って年の切り替わりを確認してみます。 というかちゃんとSimpleDateFormatのJavadocに日付パターン文字の説明が載っているんですが、暦週の基準年とか言われてもよくわからないですよね。

暦週の基準年とは・・・まあ詳細は参考のリンクを参照頂くとして、ざっくり言うとその週が属している年のことです。 基本的には西暦と一致するのですが、暦週の基準年では『その年最初の木曜日が含まれている週』が、今年で言うと2015年になります。 実際どういう挙動なのか、サンプルコードで確認してみます。

サンプルコード

   /**
    * 日付のフォーマットによる出力の違いを確認する
    */
    @Test
    public void yyyyとYYYYの出力の違いを確認() {
        // 問題が発生する日付を取得
        Date date = DateUtil.createDate("20141228");
        
        SimpleDateFormat formatSY = new SimpleDateFormat("yyyyMMdd");
        SimpleDateFormat formatLY = new SimpleDateFormat("YYYYMMdd");
        assertThat(formatSY.format(date), is("20141228"));
        assertThat(formatLY.format(date), is("20151228"));
        
        // ■暦週は月曜始まりなのに日曜から2015年になっている理由の確認
        Calendar cl = formatLY.getCalendar();
        
        // 暦週をサポートしているかを確認
        assertThat(cl.isWeekDateSupported(), is(true));
        // ロケールが日本の場合の週の始まりは日曜日⇒だから28日から2015年になっている
        assertThat(cl.getFirstDayOfWeek(), is(Calendar.SUNDAY));
    }

java_programming/DateUtilTest.java at master · seriwb/java_programming · GitHub

そういえばおまじないのようにyyyyMMddと書きますが、0埋めを考えなければ(あとSimpleDateFormatのインスタンスが使い捨てであれば)yMdでもいいんですよね。 なのでフォーマットをyYYYMMddとかにしてしまうと、年のフォーマットが2つ指定されたことになってしまって「201420151228」という結果が出力されます。タイポに気をつけねば。。

ログインエラーが起きたのは29日じゃ?

サンプルコードでは2014/12/28(日)で確認してますが、実際にTwitterのログインエラーが発生し始めたのは29日(月)ですよね。なぜ28日にエラーにならなかったのか。おそらくですが不具合を引き起こしたプログラムでは、ISO週(ISO 8601)を採用していたのではないかと思います。

サンプルコードのように、ロケールが日本の場合、週の始まりが日曜日なので2014/12/28(日)から2015年になっていますが、ISO週では月曜が週の始まりなので、ログイン出来ない問題が発生したのが日曜でなく月曜になったのではないかなと。

12/29 00:00 UTCから問題が発生していたようです。

エンジニアに想いを馳せて

言語が違っているとはいえ、コード観点で見るとこのくらいのミスであれば「あーあ、やってしまったね、どんまい」みたいな感じなのですが、 システムの規模観点から見ると、ログイン出来ないというのはサービスとしては致命的なだけに、 担当のエンジニアは胃が痛かったのではないでしょうか。年末だというのに、お疲れ様です。

参考

Gradle小ネタ

しょうもないんですが、Gradle、というかJUnit関連でちょっとエラーがあったのでメモをば。

今回の動作確認用コードはGradleでEclipseJavaプロジェクトを作成したものに、 JUnit 4.11をmavenCentralから取ってきて依存性解決を図っていたのですが、 プロジェクト自体は結構前に作成したものだったので、コード書く前はEclipseのメニューから「Refresh Dependencies」を実行していました。 参考にするためJava逆引きレシピのコードを読み込んだところ、TestWatcherのskippedをOverrideしているところでエラーが発生しており、エラー内容を見てみると「TestWatcherにskippedとか無いよ」ということでした。いやいや実装されてますやん。

ね?と思いつつ、一応デコンパイルしてみたら、まあ無いんですよね。

なぜskippedがないのだ

クラスパスを見てみたら、参照しているJUnitが4.10-devみたいな名前になってたんですよね。。あ、なんかダメそうだなと。

もう4.11は出ているはずなので、再度「Refresh Dependencies」をしてみたのですが依存性は変わらず、「Refresh All」を実行してようやく変えることが出来ました。

定期的に実行するのであれば「Refresh Dependencies」ではなく「Refresh All」の方がいいみたいです。