Android - GregorianCalendar 显示错误的月份

Posted

技术标签:

【中文标题】Android - GregorianCalendar 显示错误的月份【英文标题】:Android - GregorianCalendar displays wrong month 【发布时间】:2020-09-05 08:03:39 【问题描述】:

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

所以,我有一个包含 GregorianCalendar 类型的 dateOfOrigin 的数据类。使用 gson 我转换所有 json 并返回一个包含所有位置的 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

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

数据类位置 (
    有效ID:整数,
    值名称:字符串,
    val图像:字符串,
    val 街道:字符串,
    val 城市:字符串,
    val zip:整数,
    @SerializedName("date_of_origin")
    val originDate : GregorianCalendar?,
    val停车:布尔=真,
    var imageBitmap : 位图? =空
)

每当我尝试像这样设置 dateText 时:

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

【问题讨论】:

Calender.MONTH 从 0 开始(对于一月,link),因此您必须减去一个才能获得正确的日期。 你好,我认为它是 this 的副本。那是因为 android 日历 MONTH 从 0 位置开始 使用java.time... 在这里它有多直观(或不直观)并不重要,因为在这种情况下它工作正常,它只是不是 OP 所期望的输出,但它不会错了@Hawklike 我建议你不要使用GregorianCalendar。该课程设计不良且早已过时。而是使用来自java.time, the modern Java date and time API 的LocalDate 【参考方案1】:

GregorianCalendar 表示月份,数字范围从011。这意味着数字 0 表示为 1 月,11 表示为 12 月。

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

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

【讨论】:

没有理由使用 Joda Time 另外,你不需要subtract 而不是add 吗? 我认为你的答案的最后一部分根本没有必要,因为 api 和实现都使用相同的逻辑 - 只是因为它没有显示 OP 认为它会在这种情况下实际上并没有出错 谢谢!我仍然不明白为什么它会在内部转换为 3 月 1 日...尝试如下创建 GregorianCalendar 的实例,但仍返回 2 月 1 日而不是 2 月 30 日: originDate?.let val d = GregorianCalendar( it.get(Calendar.YEAR), it.get(Calendar.MONTH) - 1, it.get(Calendar.DAY_OF_MONTH)) //.... 因为 2 月 30 日是无稽之谈。具体来说,2016 年是闰年,所以 2 月有 29 天。因此,2 月 30 日转换为 3 月 1 日(溢出)。【参考方案2】:

Answer by Hawklike 是正确的。你被GregorianCalendar 类使用的疯狂月份编号方案欺骗了。避免此类的众多原因之一。


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
    )
)

2021 年 1 月 23 日

详情

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

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

GregorianCalendar 已替换为 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 枚举作为月份。请注意,java.time 使用正常编号,1-12 表示 1 月至 12 月,这与 GregorianCalendar 不同。

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

要生成文本,请使用DateTimeFormatter 类。您想要的格式恰好与英国使用的本地化格式相匹配。所以让 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。

2021 年 1 月 23 日


关于java.time

java.time 框架内置于 Java 8 及更高版本中。这些类取代了麻烦的旧 legacy 日期时间类,例如 java.util.DateCalendarSimpleDateFormat

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

Joda-Time 项目现在位于maintenance mode,建议迁移到java.time 类。

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

从哪里获得 java.time 类?

Java SE 8Java SE 9Java SE 10Java SE 11 和更高版本 - 具有捆绑实现的标准 Java API 的一部分。 Java 9 添加了一些小功能和修复。 Java SE 6Java SE 7 大部分 java.time 功能在ThreeTen-Backport 中向后移植到 Java 6 和 7。 Android java.time 类的 Android 捆绑包实现的更高版本。 对于早期的 Android (ThreeTenABP 项目适应 ThreeTen-Backport(如上所述)。见How to use ThreeTenABP…

【讨论】:

以上是关于Android - GregorianCalendar 显示错误的月份的主要内容,如果未能解决你的问题,请参考以下文章

Android 逆向Android 权限 ( Android 逆向中使用的 android.permission 权限 | Android 系统中的 Linux 用户权限 )

android 21 是啥版本

Android逆向-Android基础逆向(2-2)

【Android笔记】android Toast

图解Android - Android核心机制

Android游戏开发大全的目录