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/Montreal、Africa/Casablanca 或Pacific/Auckland。切勿使用 3-4 个字母的缩写,例如 EST 或 IST,因为它们不是真正的时区,没有标准化,甚至不是唯一的 (!)。
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.Date、Calendar 和 SimpleDateFormat。
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 的试验场。您可以在这里找到一些有用的类,例如Interval、YearWeek、YearQuarter 和more。