Android中GregorianCalendar月份显示错误问题求助
这个问题的核心原因其实很常见:GregorianCalendar的月份是0-based索引(0代表一月,11代表十二月),但你的JSON里的month字段是1-based的(1代表一月,12代表十二月),直接用Gson映射会导致月份整体偏移1,还会触发Calendar自动调整无效日期的机制,这就是你遇到第二个异常的根源。
为什么会出现1 Feb 2016的异常?
你提到的JSON数据{"year":2016,"month":1,"dayOfMonth":30},如果直接让Gson解析到GregorianCalendar里,它会把month=1直接赋值给Calendar的月份字段(此时对应的是二月)。但二月没有30天,GregorianCalendar会自动进行日期滚动调整,把这个无效日期转换成三月1号。之后你在显示时给月份减1,就变成了二月1号,所以最终显示1 Feb 2016。
正确的解决方法
不能只在显示阶段给月份减1,必须在解析阶段就把JSON的1-based月份转换成0-based,这样才能避免Calendar自动调整日期的问题。这里给你两个可行的方案:
方案一:自定义Gson TypeAdapter(推荐)
写一个专门的TypeAdapter来处理GregorianCalendar的反序列化,在解析时直接修正月份的偏移:
class GregorianCalendarTypeAdapter : TypeAdapter<GregorianCalendar>() { override fun write(out: JsonWriter, value: GregorianCalendar?) { // 如果需要序列化Calendar到JSON,这里可以实现把0-based月份转回1-based value?.let { out.beginObject() out.name("year").value(it.get(Calendar.YEAR)) out.name("month").value(it.get(Calendar.MONTH) + 1) out.name("dayOfMonth").value(it.get(Calendar.DAY_OF_MONTH)) out.endObject() } ?: out.nullValue() } override fun read(`in`: JsonReader): GregorianCalendar? { var year = 0 var month = 0 var dayOfMonth = 0 `in`.beginObject() while (`in`.hasNext()) { when (`in`.nextName()) { "year" -> year = `in`.nextInt() "month" -> month = `in`.nextInt() - 1 // 关键:将1-based转为0-based "dayOfMonth" -> dayOfMonth = `in`.nextInt() else -> `in`.skipValue() } } `in`.endObject() val calendar = GregorianCalendar() calendar.clear() // 清除默认的时分秒等冗余信息 calendar.set(year, month, dayOfMonth) return calendar } }
然后在创建Gson实例时注册这个Adapter:
val gson = GsonBuilder() .registerTypeAdapter(GregorianCalendar::class.java, GregorianCalendarTypeAdapter()) .create()
这样解析出来的GregorianCalendar就是完全正确的日期,显示时直接调用get()方法即可,不需要再调整月份:
originDate?.let { val day = it.get(Calendar.DAY_OF_MONTH) val monthName = it.getDisplayName(Calendar.MONTH, Calendar.SHORT, Locale.getDefault()) val year = it.get(Calendar.YEAR) dateText = resources.getString(R.string.origin_date, "$day $monthName $year") }
方案二:使用中间数据类转换
如果不想自定义TypeAdapter,可以先把JSON的日期解析成一个简单的数据类,再转换成GregorianCalendar:
// 定义用于解析JSON的中间日期类 data class DateOfOrigin( val year: Int, val month: Int, val dayOfMonth: Int ) // 修改Location数据类 data class Location ( val id : Int, val name : String, val image : String, val street : String, val city : String, val zip : Int, @SerializedName("date_of_origin") val originDateRaw: DateOfOrigin?, val parking : Boolean = true, var imageBitmap : Bitmap? = null ) { // 延迟转换为GregorianCalendar,同时修正月份偏移 val originDate: GregorianCalendar? get() = originDateRaw?.let { val calendar = GregorianCalendar() calendar.clear() calendar.set(it.year, it.month - 1, it.dayOfMonth) calendar } }
这种方式更轻量化,适合不需要频繁序列化GregorianCalendar的场景。
内容的提问来源于stack exchange,提问作者DanteC

