Android - GregorianCalendar 显示错误的月份

Android - GregorianCalendar displays wrong month

我尝试在互联网上搜索并在 StackOverlflow 上发现了很多关于同一主题的问题,但找不到任何我能够理解的问题...

所以,我的数据 class 包含 GregorianCalendar 类型的 dateOfOrigin。使用 gson 我将所有 json 和 return 转换为具有所有位置的 Observable 数组。在 json 文件中,我将 dateOfOrigin 添加为对象,如下所示:

{
    "id": 6,
    "name": "Fuse",
    "image": "fuse.jpg",
    "street": "Blaesstraat 208",
    "city": "Brussels Hoofdstedelijk Gewest",
    "zip": 1000,
    "date_of_origin": {"year":1994,"month":4,"dayOfMonth":16},
    "parking": true
}

这就是我的数据 class 的样子:

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 originDate : GregorianCalendar?,
    val parking : Boolean = true,
    var imageBitmap : Bitmap? = null
)

每当我尝试像这样设置日期文本时:

originDate?.let {
    dateText = "${it.get(Calendar.DAY_OF_MONTH)} ${it.getDisplayName(Calendar.MONTH, Calendar.SHORT, Locale.getDefault())} ${it.get(Calendar.YEAR)}"

    dateText = resources.getString(R.string.origin_date, dateText)
}

它输出 16 May 1994 而不是 16 Apr 1994

我不知道如何解决这个问题...

编辑 从月份中减去 1 似乎可以解决大多数情况下的问题。不过,我有一个结果应该输出 30 Jan 2016 但显示 1 Feb 2016.

"date_of_origin": {"year":2016,"month":1,"dayOfMonth":30}

GregorianCalendar代表月份,数字在011之间。这意味着数字 0 代表一月,而 11 代表十二月。

因此,如果您的 API 未使用与 Java 实现相同的逻辑,则您需要减去 1

更新: GregorianCalendar(2016, 1, 30) 理解为 2 月 30 日。这在内部转换为 3 月 1 日,因此当您从日期中减去一个月时,您将得到 2 月 1 日。您需要创建一个 GregorianCalendar class 已经减去月份的实例,即。一月为 0,二月为 1,依此类推。

是正确的。您被 GregorianCalendar class 采用的疯狂的月份编号方案欺骗了。避免这种情况的众多原因之一 class。


tl;博士

myGregCal  
.toZonedDateTime()                           // Convert from obsolete `GregorianCalendar` class to modern `java.time.ZonedDateTime` class.
.toLocalDate()                               // Extract the date portion, without time-of-day and without time zone.
.format(                                     // Generate text representing the value of this date-time object.
    DateTimeFormatter
    .ofLocalizedDate( FormatStyle.MEDIUM )   // Automatically localize.
    .withLocale(                             // Specify a locale for the human language and cultural norms used in localization. 
        Locale.UK
    )
)

23 Jan 2021

详情

永远不要使用 GregorianCalendar。此 class 是与 Java 的最早版本捆绑在一起的日期时间 classes 的一部分。这些 class 多年前被 java.time class 中定义的现代 JSR 310.

所取代

如果您必须与尚未更新到 java.time 的代码进行互操作,请转换。调用添加到旧 classes 的新转换方法。

GregorianCalendar was replaced by ZonedDateTime

ZonedDateTime zdt = myGregCal.toZonedDateTime() ;  // From legacy class to modern class.

走向另一个方向。

ZonedDateTime zdt = ZonedDateTime.of( 2021 , Month.JANUARY , 23 , 12 , 0 , 0 , 0 , ZoneId.of( "America/Montreal" ) ) ;
GregorianCalendar myGregCal = GregorianCalendar.from( zdt ) ;

或者将那个 int 分解成多个部分。

LocalDate ld = LocalDate.of( 2021 , Month.JANUARY , 23 ) ;
LocalTime lt = LocalTime.of( 12 , 0 ) ;
ZoneId z = ZoneId.of( "America/Montreal" ) ;
ZonedDateTime zdt = ZonedDateTime.of( ld , lt , z ) ;
GregorianCalendar myGregCal = GregorianCalendar.from( zdt ) ;

或者使用月份编号而不是 Month 枚举来表示月份。请注意,与 GregorianCalendar 不同,java.time 使用合理的编号,1-12 表示一月至十二月。

LocalDate.of( 2021 , 1 , 23 )  // Same effect as Month.JANUARY. 

要生成文本,请使用 DateTimeFormatter class。您想要的格式恰好与英国使用的本地化格式相匹配。因此,让 java.time 通过调用 DateTimeFormatter.ofLocalizedDate 自动为您本地化。

Locale locale = Locale.UK ; 
DateTimeFormatter f = DateTimeFormatter.ofLocalizedDate( FormatStyle.MEDIUM ).withLocale( locale ) ;
String output = zdt2.toLocalDate().format( f ) ;

看到这个 code run live at IdeOne.com

23 Jan 2021


关于java.time

java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.

要了解更多信息,请参阅 Oracle 教程。并在 Stack Overflow 中搜索许多示例和解释。规格为 JSR 310.

Joda-Time project, now in maintenance mode, advises migration to the java.time classes.

您可以直接与数据库交换 java.time 对象。使用 JDBC driver compliant with JDBC 4.2 或更高版本。不需要字符串,不需要 java.sql.* classes。 Hibernate 5 和 JPA 2.2 支持 java.time

从哪里获得java.time classes?