【问题标题】:Java last day of month problemJava每月最后一天问题
【发布时间】:2010-09-08 21:29:09
【问题描述】:

我正在尝试创建一个涵盖整月的日期范围,即

[开始日期;结束日期]

因此,我有一个参考日期并尝试从中创建一个新日期。我对“endDate”有疑问,因为我希望它接近一天的结束时间(即 23:59:59)。

我使用的代码如下:

  public static Date previousMonthLastDate(Date referenceDate) {
    Calendar calendar = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
    calendar.setTime(referenceDate);
    calendar.add(Calendar.MONTH, -1); // move to the previous month
    int lastDay = calendar.getActualMaximum(Calendar.DAY_OF_MONTH);
    calendar.set(Calendar.DAY_OF_MONTH, lastDay);
    // set the time to be the end of the day
    calendar.set(Calendar.HOUR_OF_DAY, 23);
    calendar.set(Calendar.MINUTE, 59);
    calendar.set(Calendar.SECOND, 59);

    return calendar.getTime();
  }

此代码在 Android emulator 上按预期运行。但是,在真实手机上运行它会给出错误的日期。因此,我假设这是某种时区问题。

在电话中,不是说 31/August/2010,而是说 01/September/2010。该值接缝设置在将 HOUR_OF_DAY 设置为 23 的代码行之后。

有什么想法吗?

谢谢。

【问题讨论】:

  • 好吧,我刚刚修好了...时间 (HOUR_OF_DAY) 必须设置为 22 而不是 23,因为该字段是从零开始的。不知道为什么它可以在模拟器上运行而不是在手机上运行。
  • 即使 HOUR_OF_DAY 从 0 开始,23 仍然是有效小时,只有 24 超出范围。
  • Damien:可能是因为“23:59:59”是第二天,所以它假设实际上是第二天?
  • 无论哪种方式,它在模拟器和设备中的行为都应该相同。我建议提交错误报告。
  • 顺便说一句,日期时间工作的范围通常使用半开放方法更好地表示,其中开始是包容性的,而结束是独占的。因此,一个月从第一天的第一刻开始,一直到但不包括下个月第一天的第一刻。好处包括不尝试将结束解析为任何特定的分辨率(毫秒、微秒、纳秒)。

标签: java android date calendar


【解决方案1】:

我无法回答为什么会这样,但您是否尝试过将其设置为下个月的第一天并减去一秒/毫秒?

calendar.set(Calendar.DAY_OF_MONTH, 1);
calendar.set(Calendar.HOUR_OF_DAY, 0);
calendar.set(Calendar.MINUTE, 0);
calendar.set(Calendar.SECOND, 0);
calendar.set(Calendar.MILLISECOND, 0);
calendar.add(Calendar.MILLISECOND, -1);

【讨论】:

  • 在任何计算中要么使用不等式,要么简单地使用不等式(即在下个月的第一天之前)。这样你就不会被闰秒或时钟变化的日子弄糊涂了......当然,如果你只是显示值,而不是在任何计算中使用它,那么这里的答案是好的。
【解决方案2】:

我从事这样的工作:

使用此代码,我将日期设置为间隔:

Date day = new Date()

有了这段代码,我得到了区间:

Calendar startDate = Calendar.getInstance();
Calendar endDate = Calendar.getInstance();

// Set time
startDate.setTime(day);
endDate.setTime(day);

startDate.set(Calendar.DAY_OF_MONTH, 1);
startDate.set(Calendar.HOUR_OF_DAY, 0);
startDate.set(Calendar.MINUTE, 0);
startDate.set(Calendar.SECOND, 0);
startDate.set(Calendar.MILLISECOND, 0);

endDate.set(Calendar.DAY_OF_MONTH, endDate.getActualMaximum(Calendar.DAY_OF_MONTH));
endDate.set(Calendar.HOUR_OF_DAY, 23);
endDate.set(Calendar.MINUTE, 59);
endDate.set(Calendar.SECOND, 59);

【讨论】:

    【解决方案3】:

    如果您希望时区取决于手机设置,则不应在创建日历时强制使用时区。只需使用:

    Calendar calendar = Calendar.getInstance();
    

    【讨论】:

      【解决方案4】:

      Calendar API 为您提供了开箱即用的功能,这里有一个技巧来完成它:

      // Get the instance of the Calendar.
      Calendar calendar = Calendar.getInstance();
      // Set the date of the First day of Month
      calendar.set(Calendar.DAY_OF_MONTH, 1);
      // Roll it to previous day of Year to get the Last day of Month
      calendar.roll(Calendar.DAY_OF_YEAR, -1);
      

      编辑:您还必须设置小时、分钟、秒和毫秒。

      【讨论】:

        【解决方案5】:
        calendar.set(Calendar.DAY_OF_MONTH, calendar.getActualMaximum(Calendar.DAY_OF_MONTH));
        

        【讨论】:

          【解决方案6】:

          tl;博士

          ZoneId z = ZoneId.of( "America/Montreal" );
          LocalDate today = LocalDate.now( z );
          ZonedDateTime start = today.with( TemporalAdjusters.firstDayOfMonth() )
                                     .atStartOfDay( z ) ;
          ZonedDateTime stop = today.with( TemporalAdjusters.firstDayOfNextMonth() )
                                    .atStartOfDay( z ) ;
          
          • 确定日期需要时区。
          • 明确指定比隐式依赖 JVM 当前的默认时区要好。

          避免遗留类

          您正在使用麻烦的旧日期时间类,现在是遗留的,被 java.time 类所取代。

          使用 java.time

          使用 java.time 类可以轻松完成这项工作。

          LocalDate

          LocalDate 类表示没有时间和时区的仅日期值。

          时区

          时区对于确定日期至关重要。对于任何给定的时刻,日期在全球范围内因区域而异。例如,Paris France 中午夜过后几分钟是新的一天,而 Montréal Québec 中仍然是“昨天”。

          continent/region 的格式指定proper time zone name,例如America/MontrealAfrica/CasablancaPacific/Auckland。切勿使用 3-4 个字母的缩写,例如 ESTIST,因为它们不是真正的时区,没有标准化,甚至不是唯一的 (!)。

          ZoneId z = ZoneId.of( "America/Montreal" );
          LocalDate today = LocalDate.now( z );
          

          始终明确指定时区。如果省略,则隐式应用 JVM 的当前默认时区。 JVM 中任何应用程序的任何代码都可以随时更改此默认值。因此,如果至关重要,请询问用户所需/预期的时区。如果不是很重要,您可以明确要求默认值以在代码中明确说明您的意图,而不是依赖隐式默认值的歧义。

          ZoneId z = ZoneId.systemDefault();  // Get JVM’s current default time zone.
          LocalDate today = LocalDate.now( z );
          

          TemporalAdjuster

          TemporalAdjuster 接口提供了可以调整日期时间值的类。 TemporalAdjusters 类提供了几个方便的实现。

          LocalDate firstOfThisMonth = today.with( TemporalAdjusters.firstDayOfMonth() );
          

          ZonedDateTime

          要将仅日期转换为日期与时间,请应用时区 ZoneId 以获取 ZonedDateTime 对象。

          不要假设一天的第一刻是 00:00:00。由于夏令时 (DST) 等异常情况,第一时刻可能类似于时间 01:00:00。让 java.time 通过调用 atStartOfDay 来解决这个问题。

          ZonedDateTime zdtStartOfMonth = firstOfThisMonth.atStartOfDay( z );
          

          半开

          您试图确定本月的最后一刻是错误的做法。最后一刻有一个无限可分的小数秒。尝试解析到特定的粒度是不明智的,因为不同的系统使用不同的粒度。旧的 Java 日期时间类使用毫秒,一些数据库(如 Postgres)使用微秒,java.time 类使用纳秒,而其他系统使用其他变体。

          在日期时间工作中常用来定义时间跨度的更明智的方法是半开放式,其中开始是包含,而结尾是排他。这意味着一个月从当月第一天的第一刻开始,一直持续到(但不包括)下个月的第一刻

          LocalDate firstOfNextMonth = today.with( TemporalAdjusters.firstOfNextMonth() );
          

          调整到时区以获得特定时刻。

          ZonedDateTime zdtStartOfNextMonth = firstOfNextMonth.atStartOfDay( z );
          

          比较一个时刻和这个时间跨度的逻辑是“这个时刻 (a) 等于还是在开始之后,并且 (b) 小于结束?”。请注意“b”部分中缺少“或等于”。这意味着我们正在接近但不包括结局。

          此外,“a”部分的更简短的说法是“不在开头之前”。所以我们可以更简单地问,“这一刻不是在开始之前还是在结束之前?”。

          ZonedDateTime moment = ZonedDateTime.now( z );
          Boolean spanContainsMoment = ( ! moment.isBefore( zdtStartOfMonth ) ) && ( moment.isBefore( zdtStartOfNextMonth ) ) ;
          

          顺便说一下,用于格式化时间跨度的文本表示的标准 ISO 8601 格式使用斜杠字符连接开头和结尾。

          String output = zdtStartOfMonth.toString() + "/" + zdtStartOfNextMonth.toString() ;
          

          Interval

          您可以使用ThreeTen-Extra 库中的Interval 类来表示这段时间。该类以Instant 对象跟踪UTC 的开始和结束。您可以从ZonedDateTime 对象中提取Instant

          Interval interval = Interval.of( zdtStartOfMonth.toInstant() , zdtStartOfNextMonth.toInstant() );
          

          YearMonth

          顺便说一句,您可能会发现YearMonth 类在您的工作中很有用。


          关于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 类?

          ThreeTen-Extra 项目通过附加类扩展了 java.time。该项目是未来可能添加到 java.time 的试验场。您可以在这里找到一些有用的类,例如IntervalYearWeekYearQuartermore

          【讨论】:

            猜你喜欢
            • 2014-05-10
            • 2023-04-10
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2021-12-11
            相关资源
            最近更新 更多