简体   繁体   English

Java最后一天的问题

[英]Java last day of month problem

I'm trying to create a date range to cover a full month, ie 我正在尝试创建一个涵盖整整一个月的日期范围,即

[startDate; [开始日期; endDate] 结束日期]

As such, I have a reference date and try to create a new date from it. 因此,我有一个参考日期,并尝试从中创建一个新的日期。 I'm having problem with the "endDate" because I want it to be near the end of the day (ie 23:59:59). 我对“endDate”有疑问,因为我希望它接近一天结束时(即23:59:59)。

The code I'm using is the following: 我正在使用的代码如下:

  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();
  }

This code is working as expected on the Android emulator . 此代码在Android 模拟器上按预期工作。 However, running it on a real phone gives the wrong date. 但是,在真实手机上运行它会给出错误的日期。 As such, I'm assuming it is some kind of timezone problem. 因此,我认为这是某种时区问题。

On the phone, instead of giving say 31/August/2010, it gives 01/September/2010. 在电话上,而不是说2010年8月31日,它给出了2010年9月1日。 This value seams to be set after the line of code that sets the HOUR_OF_DAY to 23. 此值接缝将在将HOUR_OF_DAY设置为23的代码行之后设置。

Any ideas? 有任何想法吗?

Thanks. 谢谢。

I working in something like that: 我在做类似的事情:

With this code, I set the day in interval: 使用此代码,我在间隔中设置日期:

Date day = new Date()

With this code, I get interval: 使用此代码,我得到间隔:

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

I can't answer why it's happening, but have you tried setting it to the first day of the next month and subtracting one second/millisecond? 我无法回答为什么会发生这种情况,但您是否尝试将其设置为下个月的第一天并减去一秒/毫秒?

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

If you want the timezone to depend on the phone settings, you shouldn't force a timezone when creating your calendar. 如果您希望时区依赖于手机设置,则在创建日历时不应强制使用时区。 Just use: 只需使用:

Calendar calendar = Calendar.getInstance();

The Calendar API gives you this functionality out of the box, here is a trick to get it done : 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);

Edit : You also have to set the Hours, Minutes, Seconds and Milliseconds. 编辑:您还必须设置小时,分钟,秒和毫秒。

calendar.set(Calendar.DAY_OF_MONTH, calendar.getActualMaximum(Calendar.DAY_OF_MONTH));

tl;dr 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 ) ;
  • Determining a date requires a time zone. 确定日期需要时区。
  • Better to specify explicitly than rely implicitly on JVM's current default time zone. 最好明确指定,而不是隐式依赖JVM的当前默认时区。

Avoid legacy classes 避免遗留类

You are using troublesome old date-time classes, now legacy, supplanted by the java.time classes. 您正在使用麻烦的旧日期时间类,现在是旧的,取而代之的是java.time类。

Using java.time 使用java.time

This work is much easier with the java.time classes. 使用java.time类可以更轻松地完成这项工作。

LocalDate

The LocalDate class represents a date-only value without time-of-day and without time zone. LocalDate类表示没有时间且没有时区的仅日期值。

Time zone 时区

A time zone is crucial in determining a date. 时区对于确定日期至关重要。 For any given moment, the date varies around the globe by zone. 对于任何给定的时刻,日期在全球范围内因地区而异。 For example, a few minutes after midnight in Paris France is a new day while still “yesterday” in Montréal Québec . 例如, 法国巴黎午夜过后几分钟是新的一天,而在魁北克蒙特利尔仍然是“昨天”。

Specify a proper time zone name in the format of continent/region , such as America/Montreal , Africa/Casablanca , or Pacific/Auckland . continent/region的格式指定适当的时区名称 ,例如America/MontrealAfrica/CasablancaPacific/Auckland Never use the 3-4 letter abbreviation such as EST or IST as they are not true time zones, not standardized, and not even unique(!). 切勿使用3-4字母缩写,例如ESTIST因为它们不是真正的时区,不是标准化的,甚至不是唯一的(!)。

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

Always specify the time zone explicitly. 始终明确指定时区。 If omitted, the JVM's current default time zone is implicitly applied. 如果省略,则隐式应用JVM的当前默认时区。 This default can be changed at any moment by any code of any app within the JVM. 可以随时通过JVM中任何应用程序的任何代码更改此默认值。 So if crucial, ask the user for the desired/expected time zone. 因此,如果关键,请询问用户所需/预期的时区。 If not crucial, you can ask for the default explicitly to make your intentions clear in your code rather than the ambiguity of relying on the implicit default. 如果不是至关重要,您可以明确要求默认值,以便在代码中明确表达您的意图,而不是依赖隐式默认值的模糊性。

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

TemporalAdjuster

The TemporalAdjuster interface provides for classes that can adjust date-time values. TemporalAdjuster接口提供可以调整日期时间值的类。 The TemporalAdjusters class provides several handy implementations. TemporalAdjusters类提供了几个方便的实现。

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

ZonedDateTime

To turn that date-only into a date-with-time-of-day, apply a time zone ZoneId to get a ZonedDateTime object. 要将该日期仅转换为具有时间的日期,请应用时区ZoneId以获取ZonedDateTime对象。

Do not assume the first moment of the day is 00:00:00. 不要假设当天的第一时刻是00:00:00。 Because of anomalies such as Daylight Saving Time (DST), the first moment might be something like the time 01:00:00. 由于夏令时(DST)等异常,第一时刻可能是时间01:00:00。 Let java.time figure this out by calling atStartOfDay . 让java.time通过调用atStartOfDay这个问题。

ZonedDateTime zdtStartOfMonth = firstOfThisMonth.atStartOfDay( z );

Half-Open 半开

You are taking the wrong tack by trying to determine the last moment of the month. 你试图确定一个月的最后一刻是错误的。 That last moment has an infinitely divisible fractional second. 最后一刻有一个无限可分的小数秒。 Trying to resolve to a particular granularity is ill-advised as different systems use different granularities. 尝试解析特定粒度是不明智的,因为不同的系统使用不同的粒度。 Old Java date-time classes use milliseconds, some databases such as Postgres use microseconds, the java.time classes use nanoseconds, and other systems use still other variations. 旧Java日期时间类使用毫秒,一些数据库(如Postgres使用微秒),java.time类使用纳秒,其他系统使用其他变体。

The wiser approach commonly used in date-time work for defining spans of time is Half-Open, where the beginning is inclusive while the ending is exclusive . 用于定义时间跨度的日期时间工作中常用的更明智的方法是半开放,其中开头是包含性的,而结尾是独占的 This means a month begins at the first moment of the day of the first of the month and runs up to, but not including, the first moment of the first of the following month. 这意味着一个月从该月的第一天的第一个时刻开始,并且一直持续到一个月的第一个时刻,但不包括该月的第一个时刻。

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

Adjust into a time zone to get a specific moment. 调整到时区以获得特定时刻。

ZonedDateTime zdtStartOfNextMonth = firstOfNextMonth.atStartOfDay( z );

The logic for comparing a moment to this span of time is “Is this moment (a) equal to or after the beginning, and (b) less than the ending?”. 将时刻与这段时间进行比较的逻辑是“这个时刻(a)是等于或者在开始之后,(b)是否小于结束?”。 Notice the lack of "or is equal" in part 'b'. 请注意“b”部分缺少“或相等”。 That means we are running up to, but not including, the ending. 这意味着我们正在努力,但不包括结尾。

Also, a shorter way of saying part 'a' is “not before the beginning”. 另外,说“a”部分的一种较短方式是“不在开始之前”。 So we can ask more simply, “Is this moment not before the beginning AND is before the ending?”. 所以我们可以更简单地问一下,“这个时刻不是在开始之前,而是在结束之前吗?”。

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

By the way, the standard ISO 8601 format for formatting a textual representation of a span of time uses a slash character to join the beginning and ending. 顺便说一下,用于格式化一段时间的文本表示的标准ISO 8601格式使用斜杠字符来加入开头和结尾。

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

Interval

You can represent this span of time using the Interval class in the ThreeTen-Extra library. 您可以使用ThreeTen-Extra库中的Interval类来表示此时间跨度。 That class tracks the beginning and ending in UTC as Instant objects. 这个类跟踪开始和UTC作为结束Instant对象。 You can extract an Instant from a ZonedDateTime object. 您可以从ZonedDateTime对象中提取Instant

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

YearMonth

By the way, you may find the YearMonth class useful in your work. 顺便说一句,您可能会发现YearMonth类对您的工作很有用。


About java.time 关于java.time

The java.time framework is built into Java 8 and later. java.time框架内置于Java 8及更高版本中。 These classes supplant the troublesome old legacy date-time classes such as java.util.Date , Calendar , & SimpleDateFormat . 这些类取代了麻烦的旧遗留日期时间类,如java.util.DateCalendarSimpleDateFormat

The Joda-Time project, now in maintenance mode , advises migration to the java.time classes. 现在处于维护模式Joda-Time项目建议迁移到java.time类。

To learn more, see the Oracle Tutorial . 要了解更多信息,请参阅Oracle教程 And search Stack Overflow for many examples and explanations. 并搜索Stack Overflow以获取许多示例和解释。 Specification is JSR 310 . 规范是JSR 310

You may exchange java.time objects directly with your database. 您可以直接与数据库交换java.time对象。 Use a JDBC driver compliant with JDBC 4.2 or later. 使用符合JDBC 4.2或更高版本的JDBC驱动程序 No need for strings, no need for java.sql.* classes. 不需要字符串,不需要java.sql.*类。

Where to obtain the java.time classes? 从哪里获取java.time类?

The ThreeTen-Extra project extends java.time with additional classes. ThreeTen-Extra项目使用其他类扩展了java.time。 This project is a proving ground for possible future additions to java.time. 该项目是未来可能添加到java.time的试验场。 You may find some useful classes here such as Interval , YearWeek , YearQuarter , and more . 您可以在这里找到一些有用的类,比如IntervalYearWeekYearQuarter ,和更多

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM