Java 日历从今天开始获取前 10 天,并将时间设置为 12:00 失败



我想得到今天之前的 10 天,并将时间设置为 12:00 午夜

我已经发现一天有 24 小时,所以 10 天会有 240 小时,所以我有

Calendar cal = Calendar.getInstance();
cal.set(Calendar.HOUR, cal.get(Calendar.HOUR) - 240);

以上有效,但现在当我想将时间设置为 12:00 时

我试过了

cal.set(Calendar.HOUR_OF_DAY, 0);
cal.set(Calendar.MINUTE, 0);

添加上述内容后,前 10 天现在重置为今天。

可能出了什么问题?

如果你使用的是java.time库,它可以更容易,你可以使用:

LocalDateTime date = LocalDateTime.of(
LocalDate.now().minusDays(10),
LocalTime.of(12, 0)
);

例如:

现在是:

2018-04-01T13:30

在 10 天之前,在 12 点它返回:

2018-03-22T12:00

您使用 https://docs.oracle.com/javase/7/docs/api/java/util/Calendar.html 和方法set具有此行为

SET(F, 值) 将日历字段 F 更改为值。此外,它设置 一个内部成员变量,用于指示日历字段 F 已 改变。尽管日历字段 f 会立即更改,但 日历的时间值(以毫秒为单位)直到下一个才会重新计算 调用 get()、getTime()、getTimeInMillis()、add() 或 roll() 进行调用。 因此,对 set() 的多次调用不会触发多个不必要的 计算。由于使用 set() 更改日历字段, 其他日历字段也可能更改,具体取决于日历 字段、日历字段值和日历系统。另外 get(f) 不一定返回由调用集合设置的值 方法在重新计算日历字段后。具体细节 由具体日历类确定。

示例:考虑最初设置为 8 月 31 日的公历, 1999. 调用集(日历.月,日历.九月)将日期设置为 1999 年 9 月 31 日。这是一个临时的内部表示形式, 如果随后调用 getTime(),则解析为 1999 年 10 月 1 日。然而,一个 在调用 getTime() 之前调用 set(Calendar.DAY_OF_MONTH, 30) 将日期设置为 1999 年 9 月 30 日,因为不会发生重新计算 在 set() 本身之后。

你给set打了三次电话,结果是最后一次set。因此,出于您的目的,您需要在set之后使用方法add

tl;博士

ZonedDateTime.now(                   // Capture the current moment as seen through the wall-clock time used by the people of a certain region (a time zone).
ZoneId.of( "Pacific/Auckland" )  // Specify a time zone using proper name, `continent/region`, never 3-4 pseudo-codes such as `PST`, `EST`, `IST`. 
)
.minusDays( 10 )                     // Go back in time ten days, adjusting for time-of-day as need be.
.toLocalDate()                       // Extract a date-only value.
.atStartOfDay(                       // Determine the first moment of that date in a certain time zone. Not always 00:00:00.
ZoneId.of( "Pacific/Auckland" )  
)
.toString()                          // Generate a String in standard ISO 8601 format.

2018-03-23T00:00+13:00[太平洋/奥克兰]

避免使用旧的日期时间类。

您正在使用麻烦的旧日期时间类,这些类几年前被java.time类所取代。

java.time

代替Calendar,使用ZonedDateTime来表示时间轴上的某个时刻,其中包含特定区域(时区)的人们使用的挂钟时间。

时区对于确定日期至关重要。对于任何给定时刻,日期因区域而异。例如,法国巴黎午夜过后几分钟是新的一天,而在魁北克蒙特利尔仍然是"昨天"。

如果未指定时区,那么 JVM 将隐式应用其当前缺省时区。该默认值可能随时更改,因此您的结果可能会有所不同。最好将所需/预期的时区显式指定为参数。

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

ZoneId z = ZoneId.of( "Africa/Tunis" ) ;
ZonedDateTime zdt = ZonedDateTime.now( z ) ;  // Capture the current moment.

如果要使用 JVM 的当前缺省时区,请请求它并作为参数传递。如果省略,将隐式应用 JVM 的当前缺省值。最好是明确的,因为默认值可以在运行时随时由 JVM 中任何应用程序的任何线程中的任何线程中的任何代码更改。

ZoneId z = ZoneId.systemDefault() ;  // Get JVM’s current default time zone.
ZonedDateTime zdt = ZonedDateTime.now( z ) ;  // Capture the current moment.

数学

使用Period指定未附加到年-月-天时间线的时间范围。

Period p = Period.ofDays( 10 ) ;

回到过去。夏令时 (DST) 等异常会自动处理,并根据需要调整一天中的时间。

ZonedDateTime zdtTenDaysAgo = zdt.minus( p ) ; 

"午夜"是一个技巧概念,模棱两可,无定形。相反,专注于"一天中的第一个时刻"的想法。

总是让java.time决定第一个时刻。不要假设一天从 00:00:00 开始。DST 等异常情况意味着一天可能在另一个时间(如 01:00:00)开始。若要获取第一个时刻,请提取仅日期LocalDate对象。指定时区以确定该日期在该位置开始的时间。

LocalDate ldTenDaysAgo = zdtTenDaysAgo.toLocalDate() ;
ZonedDateTime zdtTenDaysAgoStartOfDay = ldTenDaysAgo.atStartOfDay( z ) ;

如果要查看与 UTC 相同的时刻,请提取Instant

Instant instant = zdtTenDaysAgoStartOfDay.toInstant() ;

关于java.time

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

Joda-Time项目现在处于维护模式,建议迁移到 java.time 类。

要了解更多信息,请参阅Oracle 教程。并搜索堆栈溢出以获取许多示例和解释。规范为 JSR 310。

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

从哪里获得java.time类?

Java SE
  • 8Java SE 9及更高版本
    • 内置。
    • 具有捆绑实现的标准 Java API 的一部分。
    • Java 9添加了一些小功能和修复。
  • Java SE 6 和 Java SE 7 大部分
    • java.time 功能在ThreeTen-Backport中向后移植到 Java 6 和 7。
  • Android
    • 更高版本的 java.time 类的 Android 捆绑实现。
    • 对于早期的Android(<26),ThreeTenABP项目适应了ThreeTen-Backport(如上所述)。请参阅如何使用ThreeTenABP...

ThreeTen-Extra项目通过额外的类扩展了java.time。这个项目是未来可能添加到java.time的试验场。你可以在这里找到一些有用的类,如IntervalYearWeekYearQuarter等。

要加或减,您应该使用add方法:

cal.add(Calendar.HOUR, -240);

但要注意这一点,因为由于夏令时效应,一天并不总是与 24 小时相同:https://www.timeanddate.com/time/dst/transition.html

无论如何,日历是一个非常错误的类,最好使用java.time类(API级别26),如果java.time不可用,则使用threetenbp:http://www.threeten.org/threetenbp/

请参阅如何在Android中配置:如何在Android项目中使用ThreeTenABP

若要考虑 DST 影响,请使用 ZonedDateTime:

// current date/time 
ZonedDateTime.now()
// minus 10 days
.minusDays(10)
// set time to midnight 
.with(LocalTime.MIDNIGHT);

这将处理 DST 的复杂细节,包括 DST 从午夜开始而一天实际上从凌晨 1 点开始的情况 - 时间会自动调整。

目前尚不清楚您想要午夜(00:00)还是中午(12:00)。无论如何,如果你想要中午,只需使用LocalTime.NOON.

最新更新