我正在为rest API编写自动的bdds。 API会返回一个日期。我想获取API返回的日期与今天的当前日期之间的差额。
例如,API返回“ 2018年3月13日下午12:00”
今天的日期是“ 2018年3月11日下午12:00”
时间总是一样的,只是改变的日子。该API还将返回将来的日期。
我有这段代码:
Date currentDate = Date.from(Instant.now());
// endDate comes from the API
long diff = endDate.getTime() - currentDate.getTime();
long differenceInDays = TimeUnit.DAYS.convert(diff, TimeUnit.MILLISECONDS);
这将返回1
,但我希望它包括最后一天。我只可以在+1
的末尾添加endDate.getTime() - currentDate.getTime();
,但是我不确定这是否是正确的方法。
我还读到,这通常不是一个好的解决方案,因为它没有考虑夏令时。我不确定夏时制到来时会如何或是否会影响我的自动bdd。捕获几天差异的最佳方法是什么?
将其视为how many days do I have left until expiration
答案 0 :(得分:1)
您真正的问题是您的后端REST服务设计不良。
首先,交换的日期时间值应采用标准ISO 8601格式,而不是某些本地化的表示字符串。
在解析/生成文本时, java.time 类中默认使用标准格式。
永远不要使用可怕的Date
类。该类以及Calendar
,SimpleDateFormat
等在多年前被JSR 310中定义的 java.time 类所取代。
Date.from(Instant.now())
请不要将可怕的旧式日期时间类(Date
)与它们的替代物(Instant
)混合在一起,即现代的 java.time 类。混合这些是不必要的并且令人困惑。
java.time 类完全替换了它们的前身。
时间总是一样的,只是改变的日子。该API还将返回将来的日期。
如果您只想交换日期值,没有时间,没有时区或偏移量,请使用LocalDate
类,并交换ISO 8601格式YYYY-MM-DD,例如{{ 1}}。呼叫2018-03-11
和LocalDate.parse
。
long differentInDays = TimeUnit.DAYS.convert(diff,TimeUnit.MILLISECONDS);
将天数表示为毫秒数而不包含时区或UTC偏移量是很鲁ck的。日子并非总是24小时。它们可以是23、23.5、25或其他一些小时数。
如果您打算使用UTC来始终保持24小时工作日,请这样说。用时区或偏移量表示日期时间。例如,标准格式:LocalDate::toString
,其中2018-03-11T00:00Z
的末尾表示UTC,并发音为“ Zulu”。
因此,您的整个问题可以减少到此一线。
Z
如果您无法解决所有这些凌乱的设计问题,那么让我们继续前进,尝试使用这些凌乱的数据。
首先修复应该大写的ChronoUnit.DAYS.between(
LocalDate.now( ZoneId.of( "America/Montreal" ) ) , // Get the current date as seen in the wall-clock time used by the people of a particular region (a time zone).
LocalDate.parse( "2019-01-23" ) // Parse a string in standard ISO 8601 format for a date-only value.
) // Returns a `long` integer number of days elapsed.
/ am
。
pm
定义格式模式以匹配您的输入字符串。
指定 String input = "March 13, 2018 12:00 pm".replace( " am" , " AM" ).replace( " pm" , " PM" );
,以确定在翻译文本时要使用的人类语言和文化规范。
Locale
解析为Locale locale = Locale.US;
DateTimeFormatter f = DateTimeFormatter.ofPattern( "MMMM d, uuuu HH:mm a" );
,因为您的输入缺少时区或UTC偏移量指示符。
LocalDateTime
ldt.toString():2018-03-13T12:00
故意LocalDateTime ldt = LocalDateTime.parse( input , f );
没有时区或UTC偏移量的概念。因此,此类不能表示时刻,不是时间线上的一点。
如果您希望全天24小时工作,而不考虑各个地方(例如Daylight Saving Time (DST)的各个人使用的挂钟时间的异常情况,我们可以继续使用此类。
获取您的应用所针对的人(时区)所用的挂钟时间的当前日期。
时区对于确定日期至关重要。在任何给定时刻,日期都会在全球范围内变化。例如,Paris France午夜之后的几分钟是新的一天,而Montréal Québec仍然是“昨天”。
如果未指定时区,则JVM隐式应用其当前的默认时区。该默认值可能在运行时(!)期间change at any moment,因此您的结果可能会有所不同。最好将desired/expected time zone明确指定为参数。
以LocalDateTime
的格式指定proper time zone name,例如continent/region
,America/Montreal
或Africa/Casablanca
。切勿使用2-4个字母的缩写,例如Pacific/Auckland
或EST
,因为它们不是真正的时区,不是标准化的,甚至不是唯一的(!)。
IST
如果要使用JVM的当前默认时区,请提出要求并作为参数传递。如果省略,则会隐式应用JVM的当前默认值。最好明确一点,因为默认值可能会在运行时的任何时候被JVM中任何应用程序的任何线程中的任何代码更改。
ZoneId z = ZoneId.of( "America/Montreal" ) ;
LocalDate today = LocalDate.now( z ) ;
在该日期的中午,没有特定的时区。
ZoneId z = ZoneId.systemDefault() ; // Get JVM’s current default time zone.
计算过去的天数。
LocalDateTime ldtTodayNoon = LocalDateTime.of( today , LocalTime.NOON ) ;
当然,我们也可以只使用long daysElapsed =
ChronoUnit.DAYS.between(
ldtTodayNoon ,
ldt
)
;
而不是LocalDate
来完成此操作,但是我按照您写的问题陈述进行操作。
请注意,在给定的示例中,字符串表示过去的日期。因此,我们的天数将为负。
如果您确实要考虑某些区域某些日期的异常现象,那么应该正确地表示一个时刻,如上所述,并带有时区或UTC偏移量。
LocalDateTime
或者您今天想在所需的时区中午。如果在该区域的该日期中午不是该日的有效时间,则ZoneId z = ZoneId.of( "Africa/Tunis" ) ;
ZonedDateTime zdt = ldt.atZone( z ) ;
ZonedDateTime zdtNow = ZonedDateTime.now( z ) ;
类将进行调整。请务必阅读ZonedDateTime.of
JavaDoc,以了解该调整的算法。
ZonedDateTime
根据小数秒或整个日历天来计算经过时间。
LocalDate today = LocalDate.now( z ) ;
ZonedDateTime zdtTodayNoon = ZonedDateTime.of( today , LocalTime.NOON , z ) ;
如果您坚持以毫秒为单位进行跟踪,请致电Duration::toMillis
。
Duration d = Duration.between( zdtTodayNoon , zdt ) ; // For a calculation based in whole seconds plus a fractional second in nanoseconds without regard for a calendar, just using generic 24-hour days.
Period p = Period.between( zdtTodayNoon , zdt ) ; // For a calculation based in whole days, for a number of years-months-days based on calendar dates.
所有这些已经在Stack Overflow上讨论过很多次了。您可以通过搜索这些 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 driver或更高版本的JDBC 4.2。不需要字符串,不需要long millisecondsElapsed = d.toMillis() ; // Entire duration as a total number of milliseconds, ignoring any microseconds or nanos.
类。
在哪里获取java.time类?
ThreeTen-Extra项目使用其他类扩展了java.time。该项目为将来可能在java.time中添加内容提供了一个试验场。您可能会在这里找到一些有用的类,例如Interval
,YearWeek
,YearQuarter
和more。