日付のソートが失敗していた話

概要

型変換の認識不足により日付のソートに失敗しておりました。

Long型とInt型、型変換する際は気を付けねば、という話です。

2022年の投稿がソートで正しく表示されないバグ

投稿アプリ「みてみて」には全ての投稿をリスト表示する画面があります。

このリストでは新着順、いいね順、距離順の3パターンでソートできるようにしているのですが。。。

2022年の最新の投稿を新着順でソートした際に先頭に表示されないバグが発覚。

ロジックを見直しても一見、おかしなところも見当たらず。

そこでソートロジックを切り取り、テストアプリを作成してログを確認。

ようやく理由が判明した、というわけです。

日付をソートするテストアプリで動作確認

まずは適当に日付をセットしたリストを用意し、表示するだけのアプリを作成。

これにソート用のボタンを追加し、ボタンをタップするとソートしてリスト表示を反映させるロジックを追加。

        val btnUp = findViewById<Button>(R.id.btnUp)
        btnUp.setOnClickListener {
            Collections.sort(list, object : Comparator<List>{
                override fun compare(p0: List?, p1: List?): Int {
                    val tmpdiff = p1!!.timestamp - p0!!.timestamp
                    return tmpdiff.toInt()
                }
            })
            adapter.setList(list)
            adapter.notifyDataSetChanged()
        }

上記はアプリに組み込んだロジックを切り取ったものですが間違いに気づきましたでしょうか?

日付はLong型(1970年1月1日00:00を起点とした経過時間)で扱っています。

ソートはCollections.sortのcompareメソッドを使用。

compareは大小比較さえできればよいだろうということでLong型の差分をリターンすればよいと判断。

ただし、それだとInt型で受けれないのでLong型からInt型に型変換を追加。

そして、このロジックでソートした結果が以下となります。

ソートされていません。

ここでログを仕込み、引数1、引数2、引数2と引数1の差分(Long型)、差分の型変換(Int型)の順で出力してみると。

Long型では負なのに、Int型では正になっていることが発覚。

Long型は8バイト、Int型は4バイト。

上記で1例を見るとLong型の値、-2678400000は16進数で0xFFFF FFFF 605A DC00。

これをInt型に変換すると先頭4バイトが落ち、16進数で0x605A DC00。

つまり、1616567296。正の値になりました。

Int型への型変換で4バイトの情報だけを参照した結果、正の領域が負の領域に切り替わってしまったというわけです。

正しいロジックとは

修正するとすれば2パターンでしょうか。

パターン1)Date型に変換し比較する。

        val btnUp = findViewById<Button>(R.id.btnUp)
        btnUp.setOnClickListener {
            Collections.sort(list, object : Comparator<List>{
                override fun compare(p0: List?, p1: List?): Int {
                    val datea = Date(p0!!.timestamp)
                    val dateb = Date(p1!!.timestamp)
                    return dateb.compareTo(datea)
                }
            })
            adapter.setDateList(list)
            adapter.notifyDataSetChanged()
        }

パターン2)Long型の差分結果をもとにInt型で正負に置き換える

        val btnUp = findViewById<Button>(R.id.btnUp)
        btnUp.setOnClickListener {
            Collections.sort(list, object : Comparator<List>{
                override fun compare(p0: List?, p1: List?): Int {
                    val tmpdiff = p1!!.timestamp - p0!!.timestamp
                    var rtnval = 0
                    if (tmpdiff < 0L) {
                        rtnval = -1
                    } else if (tmpdiff == 0L){
                        rtnval = 0
                    } else {
                        rtnval = 1
                    }
                    return rtnval
                }
            })
            adapter.setDateList(list)
            adapter.notifyDataSetChanged()
        }

どちらのロジックにしろ、正しくソートされました。

早速、修正したいと思います。