【问题标题】:Is Android's Calendar.Day_Of_Month zero based?Android 的 Calendar.Day_Of_Month 是从零开始的吗?
【发布时间】:2018-12-01 08:11:27
【问题描述】:

根据 android 文档,Day_Of_Month 是从 1 开始的(即 '1' 表示该月的第一天)(请参阅 https://developer.android.com/reference/java/util/Calendar#DAY_OF_MONTH )。

但是,当我在两个 Android 设备上运行以下代码时,我得到“2018-01-02”作为结果。我错过了什么吗?

Calendar cal = new GregorianCalendar();
TimeZone tzone = TimeZone.getTimeZone("GMT");
cal.setTimeZone(tzone);

cal.set(Calendar.HOUR, 12);
cal.set(2018, 0, 1);

DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
String date = df.format(cal.getTime());

我不只是假设文档错误的原因是我有一些证据表明我的客户的一台设备将日期报告为“2018-01-01”

【问题讨论】:

  • 这只是Calendar 类的一小部分问题。 Calendar.HOUR 是上午或下午的小时,从 0 到 11,所以当你将它设置为 12 时,你应该得到一些超出范围的异常。但不,你没有。当您在当天的下午(UTC 时间)执行此操作时,它只会溢出到第二天。请参阅Basil Bourque’s answer 了解现代且更好的选择。 PS当我刚刚运行你的代码时(在UTC的上午),我得到了你所期望的2018-01-01,因为时间“仅”溢出到同一天的下午。

标签: java android calendar


【解决方案1】:

实际上,您正在做三件事会影响您的结果。

(1) 您使用的是 Calendar.HOUR,它是 12 小时制的!

Java 文档:

HOUR:
get 和 set 字段编号,指示早上的时间 或下午。 HOUR 用于 12 小时制 (0 - 11)。中午和 午夜用 0 表示,而不是 12。例如,在晚上 10:04:15.250 HOUR 是 10。

您要使用的是Calendar.HOUR_OF_DAY,它是 24 小时制的!

Java 文档:

HOUR_OF_DAY:
get 和 set 的字段编号,表示小时 日。 HOUR_OF_DAY 用于 24 小时制。例如,在 10:04:15.250 下午 HOUR_OF_DAY 是 22。

(2) 您将时间设置为 12。这会在您使用 Calendar.HOUR 时导致一些问题。

(3) 您将TimeZone "GMT" 与Calendar.HOUR 设置结合使用将导致日期递增递减,具体取决于用户所在的实际时区。

示例:

我在 CST 时区,我运行了以下代码:

Calendar cal = new GregorianCalendar();
cal.set(2018, 0, 1);
int h = 0;

cal.set(Calendar.HOUR_OF_DAY, h);
DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm");
String date1 = df.format(cal.getTime());
Log.e(TAG, "date1 = " + date1)

cal.set(Calendar.HOUR, h);
TimeZone tzone = TimeZone.getTimeZone("GMT");
cal.setTimeZone(tzone);
String date2 = df.format(cal.getTime());
Log.e(TAG, "date2 GMT = " + date2)

并在 h = 0 时得到这些结果:

date1 = 2018-01-01 00:54
date2 GMT = 2017-12-31 18:54

设置 h = 12:

date1 = 2018-01-01 12:54
date2 GMT = 2018-01-01 18:54

七个小时后我会得到:

date1 = 2018-01-01 19:54
date2 GMT = 2018-01-02 01:54

【讨论】:

    【解决方案2】:

    tl;博士

    Android 的 Calendar.Day_Of_Month 是从零开始的吗?

    没有。

    我得到“2018-01-02”……

    …报告日期为“2018-01-01”

    您的第 1 次与第 2 次问题与其他问题有关:时区。

    使用 java.time 代替那些非常麻烦的遗留类。

    OffsetDateTime.of(
        LocalDate.of( 2018 , Month.JANUARY , 1 ) , 
        LocalTime.NOON ,
        ZoneOffset.UTC
    )
    

    .toString(): 2018-01-01T12:00Z

    .format(
        DateTimeFormatter.ISO_LOCAL_DATE
    )
    

    2018-01-01

    或者覆盖区域/偏移量。

    .format(
        DateTimeFormatter.ISO_LOCAL_DATE
                         .withZone( ZoneId.of( "Pacific/Kiritimati" ) )  // Using zone 14 hours ahead of UTC. So noon UTC is “tomorrow” in Kiribati.
    )
    

    2018-01-02

    时区

    如果在 America/Los_Angeles 时区 16:00 运行您的代码,我会得到 2018-01-01。但是,如果我将您的代码更改为将小时设置为23 而不是12,我会得到2018-01-02

    所以有一个关于时区的问题。什么是“今天”和什么是“明天”取决于您所在的时区。

    与其进一步玷污我的大脑,让我提出真正的解决方案:停止使用这些糟糕的日期时间类

    java.time

    那些旧的日期时间类(DateCalendarSimpleDateFormat)在几年前被现代的 java.time 类所取代。

    显然你想在年初的中午。这是如何做。请注意合理的编号:1 月至 12 月的月份为 1-12(与旧课程不同),倒数第二天的月份为 1-31(如旧课程)。

    获取日期。

    LocalDate ld = LocalDate.of( 2018 , 1 , 1 ) ; // January 1, 2018.
    

    或者使用更易读的Month枚举。

    LocalDate ld = LocalDate.of( 2018 , Month.JANUARY , 1 ) ; // January 1, 2018.
    

    生成一个以标准 ISO 8601 格式表示该值的字符串。

    ld.toString(): 2018-01-01

    获取一天中的时间,中午。 LocalTime 类有一个常量。

    LocalTime lt = LocalTime.NOON ; 
    

    指定与 UTC 的偏移量为零,即 UTC 本身。 ZoneOffset 类有一个常量。

    ZoneOffset offset = ZoneOffset.UTC ;
    

    组合以将时刻表示为OffsetDateTime 对象。

    OffsetDateTime odt = OffsetDateTime.of( ld , lt , offset ) ;
    

    生成一个以标准 ISO 8601 格式表示该值的字符串。

    odt.toString(): 2018-01-01T12:00Z

    如果您只需要日期部分,请提取 LocalDate

    LocalDate ld = odt.toLocalDate() ;
    

    或者通过定义 DateTimeFormatter 仅使用日期部分打印字符串。

    DateTimeFormatter f = DateTimeFormatter.ISO_LOCAL_DATE ; 
    String outputOdtDateOnly = odt.format( f ) ;
    

    2018-01-01

    默认情况下,DateTimeFormatter 对象使用新字符串表示的对象的偏移量或区域。您可以选择覆盖该偏移量/区域。让我们试试看。

    ZoneId z = ZoneId.of( "Pacific/Kiritimati" );  // Most eastern (earliest) time zone is in Kiribati. https://en.wikipedia.org/wiki/Kiribati
    DateTimeFormatter fKiritimati = f.withZone( z );
    String outputOdtDateOnlyInKiribati = odt.format( fKiritimati );
    

    请注意,我们更改了格式化程序对象,而不是数据对象,而不是OffsetDateTime 对象。我们在新的格式化程序对象中添加了一个时区,之前的格式化程序保存了nullas documented。并注意 java.time 如何使用immutable objects,其中一个新对象是根据原始值实例化的,而不是更改(“变异”)原始对象。因此,我们在第一个对象的基础上得到了第二个 DateTimeFormatter 对象,但添加了我们指定的覆盖区域。

    让我们看看我们得到了什么。

    System.out.println( outputOdtDateOnlyInKiribati );
    

    2018-01-02

    惊喜!回到您的问题中的问题。基里巴斯部分地区比世界标准时间早 14 小时。因此,当它是 UTC 中午时,同时在 Pacific/Kiritimati 区域中是 14 小时后,因此“明天”是第二个。


    关于java.time

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

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

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

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

    从哪里获得 java.time 类?

    【讨论】:

    • 感谢您的详细回复。我会在某个时候考虑切换课程,但就目前而言,小时与一天中的小时的使用完美地解释了令人困惑的行为。
    猜你喜欢
    • 1970-01-01
    • 2021-08-21
    • 1970-01-01
    • 2020-06-29
    • 2022-12-25
    • 2010-12-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多