12

我正在使用 openjdk 版本1.8.0_112-release进行开发,但也需要支持以前的 JDK 版本(Java-8 之前) - 所以不能使用java.time.

我正在编写一个实用类来计算日期,以查看保存的日期是否早于当前日期,这意味着它已过期。

但是,我不确定我是否以正确的方式做到了这一点。我正在使用LocalDate类来计算天数。过期时间从用户单击保存的日期和时间开始计算。该日期将被保存,并将针对该保存的日期和时间以及当前日期和时间(即用户登录时)进行检查。

这是最好的方法吗?我想继续LocalDate上课。

import org.threeten.bp.LocalDate;

public final class Utilities {
    private Utilities() {}

    public static boolean hasDateExpired(int days, LocalDate savedDate, LocalDate currentDate) {
        boolean hasExpired = false;

        if(savedDate != null && currentDate != null) {
            /* has expired if the saved date plus the specified days is still before the current date (today) */
            if(savedDate.plusDays(days).isBefore(currentDate)) {
                hasExpired = true;
            }       
        }

        return hasExpired;
    }
}

我正在使用这样的类:

private void showDialogToIndicateLicenseHasExpired() {
    final LocalDate currentDate = LocalDate.now();
    final int DAYS_TO_EXPIRE = 3;
    final LocalDate savedDate = user.getSavedDate();

    if(hasDateExpired(DAYS_TO_EXPIRE, savedDate, currentDate)) {
        /* License has expired, warn the user */    
    }
}

我正在寻找一个考虑时区的解决方案。如果许可证设置为在 3 天内到期,并且用户要前往不同的时区。即他们可以基于小时领先或落后。许可证仍应过期。

4

7 回答 7

10

你的代码基本没问题。我会以基本相同的方式来做,只是有一两个细节不同。

正如 Hugo 已经指出的那样,我会使用java.time.LocalDate和放弃使用 ThreeTen Backport(除非它是您的代码也可以在 Java 6 或 7 上运行的特定要求)。

时区

您应该决定在哪个时区计算您的天数。如果您在代码中明确显示时区,我也更愿意。如果您的系统仅在您自己的时区使用,选择很简单,只需明确说明即可。例如:

    final LocalDate currentDate = LocalDate.now(ZoneId.of("Asia/Hong_Kong"));

请填写相关区域ID。这也将确保程序正常工作,即使有一天它碰巧在时区设置不正确的计算机上运行。如果您的系统是全球性的,您可能希望使用 UTC,例如:

    final LocalDate currentDate = LocalDate.now(ZoneOffset.UTC);

在保存用户单击“保存”时的日期时,您将希望执行类似操作,以便您的数据保持一致。

72 小时

编辑:我从您的评论中了解到,您想从保存时间开始测量 3 天,即 72 小时,以确定许可证是否已过期。为此 aLocalDate没有给你足够的信息。它只是一个没有时钟时间的日期,例如公元 2017 年 5 月 26 日。还有一些其他选项:

  • Instant是一个时间点(甚至具有纳秒精度)。无论用户是否移动到另一个时区,这是确保 72 小时后到期的简单解决方案。
  • ZonedDateTime表示日期和时间以及时区,例如 2017 年 5 月 29 日 AD 19:21:33.783 在偏移 GMT+08:00[Asia/Hong_Kong]。如果您想提醒用户保存的时间是什么时候,ZonedDateTime您是否允许您显示该信息以及计算保存日期的时区。
  • finallyOffsetDateTime也可以,但它似乎没有给你太多其他两个的优点,所以我不会详细说明这个选项。

由于所有时区的瞬间都相同,因此在获取当前瞬间时无需指定时区:

    final Instant currentDate = Instant.now();

给 an 添加 3 天Instant有点不同LocalDate,但其余逻辑是相同的:

public static boolean hasDateExpired(int days, Instant savedDate, Instant currentDate) {
    boolean hasExpired = false;

    if(savedDate != null && currentDate != null) {
        /* has expired if the saved date plus the specified days is still before the current date (today) */
        if (savedDate.plus(days, ChronoUnit.DAYS).isBefore(currentDate)) {
            hasExpired = true;
        }       
    }

    return hasExpired;
}

ZonedDateTime另一方面,的使用LocalDate与代码中的完全一样:

    final ZonedDateTime currentDate = ZonedDateTime.now(ZoneId.of("Asia/Hong_Kong"));

如果您想从程序运行的 JVM 获取当前时区设置:

    final ZonedDateTime currentDate = ZonedDateTime.now(ZoneId.systemDefault());

现在如果你声明public static boolean hasDateExpired(int days, ZonedDateTime savedDate, ZonedDateTime currentDate),你可以像以前一样做:

        /* has expired if the saved date plus the specified days is still before the current date (today) */
        if (savedDate.plusDays(days).isBefore(currentDate)) {
            hasExpired = true;
        }       

即使两个ZonedDateTime对象位于两个不同的时区,这也将执行正确的比较。因此,无论用户是否前往不同的时区,在许可证到期前她/他都不会获得更少或更多的小时数。

于 2017-05-26T19:23:12.043 回答
8

您可以使用ChronoUnit.DAYS(在org.threeten.bp.temporal包中,或者java.time.temporal如果您使用 java 8 本机类)来计算 2 个LocalDate对象之间的天数:

if (savedDate != null && currentDate != null) {
    if (ChronoUnit.DAYS.between(savedDate, currentDate) > days) {
        hasExpired = true;
    }
}

编辑(赏金解释后)

对于这个测试,我使用的是threetenbp 1.3.4版本

由于您想要一个即使用户在不同时区也能工作的解决方案,所以您不应该使用LocalDate,因为此类不处理时区问题。

我认为最好的解决方案是使用Instant类。它代表一个时间点,无论您在哪个时区(此时,世界上的每个人都在同一时刻,尽管本地日期和时间可能因您所在的位置而异)。

实际上Instant总是在UTC 时间- 一个独立于时区的标准,因此非常适合您的情况(因为您想要一个独立于用户所在时区的计算)。

所以 yoursavedDatecurrentDatemust be都是Instant,你应该计算它们之间的差异。

现在,一个微妙的细节。您希望在 3 天后到期。对于我所做的代码,我做出以下假设:

  • 3 天 = 72 小时
  • 72 小时后的 1 分之一秒,过期了

第二个假设对于我实现解决方案的方式很重要。我正在考虑以下情况:

  1. currentDate不到 72 小时后savedDate-未过期
  2. currentDate正好是 72 小时后savedDate-未过期(或过期?请参阅下面的评论)
  3. currentDate超过 72 小时后savedDate(甚至几分之一秒) -过期

该类Instant具有纳秒精度,因此在案例3中,我认为它已过期,即使 72 小时后为 1 纳秒:

import org.threeten.bp.Instant;
import org.threeten.bp.temporal.ChronoUnit;

public static boolean hasDateExpired(int days, Instant savedDate, Instant currentDate) {
    boolean hasExpired = false;

    if (savedDate != null && currentDate != null) {
        // nanoseconds between savedDate and currentDate > number of nanoseconds in the specified number of days
        if (ChronoUnit.NANOS.between(savedDate, currentDate) > days * ChronoUnit.DAYS.getDuration().toNanos()) {
            hasExpired = true;
        }
    }

    return hasExpired;
}

请注意,我曾经ChronoUnit.DAYS.getDuration().toNanos()获得一天中的纳秒数。最好依赖 API,而不是硬编码容易出错的大数字。

我做了一些测试,使用相同时区和不同时区的日期。我使用ZonedDateTime.toInstant()方法将日期转换为Instant

import org.threeten.bp.ZoneId;
import org.threeten.bp.ZonedDateTime;

// testing in the same timezone
ZoneId sp = ZoneId.of("America/Sao_Paulo");
// savedDate: 22/05/2017 10:00 in Sao Paulo timezone
Instant savedDate = ZonedDateTime.of(2017, 5, 22, 10, 0, 0, 0, sp).toInstant();
// 1 nanosecond before expires (returns false - not expired)
System.out.println(hasDateExpired(3, savedDate, ZonedDateTime.of(2017, 5, 25, 9, 59, 59, 999999999, sp).toInstant()));
// exactly 3 days (72 hours) after saved date (returns false - not expired)
System.out.println(hasDateExpired(3, savedDate, ZonedDateTime.of(2017, 5, 25, 10, 0, 0, 0, sp).toInstant()));
// 1 nanosecond after 3 days (72 hours) (returns true - expired)
System.out.println(hasDateExpired(3, savedDate, ZonedDateTime.of(2017, 5, 25, 10, 0, 0, 1, sp).toInstant()));

// testing in different timezones (savedDate in Sao Paulo, currentDate in London)
ZoneId london = ZoneId.of("Europe/London");
// In 22/05/2017, London will be in summer time, so 10h in Sao Paulo = 14h in London
// 1 nanosecond before expires (returns false - not expired)
System.out.println(hasDateExpired(3, savedDate, ZonedDateTime.of(2017, 5, 25, 13, 59, 59, 999999999, london).toInstant()));
// exactly 3 days (72 hours) after saved date (returns false - not expired)
System.out.println(hasDateExpired(3, savedDate, ZonedDateTime.of(2017, 5, 25, 14, 0, 0, 0, london).toInstant()));
// 1 nanosecond after 3 days (72 hours) (returns true - expired)
System.out.println(hasDateExpired(3, savedDate, ZonedDateTime.of(2017, 5, 25, 14, 0, 0, 1, london).toInstant()));

PS:对于案例2 currentDate在 savedDate 之后正好 72 小时 -未过期 - 如果您希望它过期,只需将if上面的内容更改为使用>=而不是>

if (ChronoUnit.NANOS.between(savedDate, currentDate) >= days * ChronoUnit.DAYS.getDuration().toNanos()) {
    ... // it returns "true" for case 2
}

如果您不想要纳秒级精度而只想比较日期之间的天数,您可以按照@Ole VV 的回答进行操作。我相信我们的答案非常相似(我怀疑代码是等效的,尽管我不确定),但我没有测试足够的案例来检查它们在任何特定情况下是否不同。

于 2017-05-26T18:20:20.850 回答
5
于 2017-05-26T20:51:03.743 回答
2

我认为最好使用这个:

Duration.between(currentDate.atStartOfDay(), savedDate.atStartOfDay()).toDays() > days;

Duration类放在java.time包中。

于 2017-05-26T19:09:29.117 回答
1

由于这个问题没有得到“足够的回应”,我添加了另一个答案:

我用过“SimpleDateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));” 将时区设置为 UTC。因此不再有时区(所有日期/时间都将设置为 UTC)。

保存日期设置为 UTC。

dateTimeNow也设置为 UTC,将过期的“天数”(负数)添加到 dateTimeNow中。

新的 Date expiresDate使用dateTimeNow的长毫秒

检查是否已保存日期。之前(过期日期)


package com.chocksaway;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;

public class ExpiredDate {
    private static final long DAY_IN_MS = 1000 * 60 * 60 * 24;

    private static boolean hasDateExpired(int days, java.util.Date savedDate) throws ParseException {
        SimpleDateFormat dateFormatUtc = new SimpleDateFormat("yyyy-MMM-dd HH:mm:ss");
        dateFormatUtc.setTimeZone(TimeZone.getTimeZone("UTC"));

        // Local Date / time zone
        SimpleDateFormat dateFormatLocal = new SimpleDateFormat("yyyy-MMM-dd HH:mm:ss");

        // Date / time in UTC
        savedDate = dateFormatLocal.parse( dateFormatUtc.format(savedDate));

        Date dateTimeNow = dateFormatLocal.parse( dateFormatUtc.format(new Date()));

        long expires = dateTimeNow.getTime() + (DAY_IN_MS * days);

        Date expiresDate = new Date(expires);

        System.out.println("savedDate \t\t" + savedDate + "\nexpiresDate \t" + expiresDate);

        return savedDate.before(expiresDate);
    }

    public static void main(String[] args) throws ParseException {
        Calendar cal = Calendar.getInstance();
        cal.add(Calendar.DATE, 0);

        if (ExpiredDate.hasDateExpired(-2, cal.getTime())) {
            System.out.println("expired");
        } else {
            System.out.println("not expired");
        }

        System.out.print("\n");

        cal.add(Calendar.DATE, -3);

        if (ExpiredDate.hasDateExpired(-2, cal.getTime())) {
            System.out.println("expired");
        } else {
            System.out.println("not expired");
        }
    }
}

运行此代码会给出以下输出:

savedDate       Mon Jun 05 15:03:24 BST 2017
expiresDate     Sat Jun 03 15:03:24 BST 2017
not expired

savedDate       Fri Jun 02 15:03:24 BST 2017
expiresDate     Sat Jun 03 15:03:24 BST 2017
expired

所有日期/时间均为 UTC。首先是没有过期。第二个过期(savedDate 在 expiresDate 之前)。

于 2017-06-05T15:15:52.063 回答
0

public static boolean hasDateExpired(int days, java.util.Date savedDate) {
    long expires = savedDate().getTime() + (86_400_000L * days);
    return System.currentTimeMillis() > expires;
}

适用于旧 JRE 就好了。Date.getTime() 给出毫秒 UTC,因此时区甚至不是一个因素。神奇的 86'400'000 是一天中的毫秒数。

如果您只使用 long for savedTime,您可以进一步简化此操作,而不是使用 java.util.Date。

于 2017-05-31T15:07:58.200 回答
0

我已经构建了一个简单的实用程序类ExpiredDate,具有 TimeZone(例如 CET)、expiredDate、expireDays 和 differenceInHoursMillis。

我使用 java.util.Date 和 Date.before(expiredDate):

查看 Date()乘以expiryDays加上(时区差乘以expiryDays)是否在expiredDate之前。

任何早于 expiredDate 的日期都是“过期的”。

通过添加 (i) + (ii) 创建一个新日期:

(一) . 我使用一天中的毫秒数为 (DAY_IN_MS = 1000 * 60 * 60 * 24) 乘以 expireDays 的(数量)。

+

(二) . 为了处理不同的时区,我找到了默认时区(对我来说是 BST)和传递给 ExpiredDate 的时区(例如 CET)之间的毫秒数。对于CET,相差一小时,即 3600000 毫秒。这乘以(数量)expireDays。

新的 DateparseDate()返回。

如果新日期expiredDate之前-> 将 expired 设置为 True。 dateTimeWithExpire.before(expiredDate);

我创建了 3 个测试:

  1. 设置有效期7天,expireDays = 3

    未过期 (7 天大于 3 天)

  2. 设置到期日期/时间,并将 expireDays 设置为 2 天

    未过期- 因为 CET 时区增加了两个小时(每天一小时)到 dateTimeWithExpire

  3. 设置有效期1天,expireDays = 2(1天小于2天)

过期是真的


package com.chocksaway;

import java.text.ParseException;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;

public class ExpiredDate {
    /**
     * milliseconds in a day
    */
    private static final long DAY_IN_MS = 1000 * 60 * 60 * 24;
    private String timeZone;
    private Date expiredDate;
    private int expireDays;
    private int differenceInHoursMillis;

    /**
     *
     * @param timeZone - valid timezone
     * @param expiredDate - the fixed date for expiry
     * @param expireDays - the number of days to expire
    */
    private ExpiredDate(String timeZone, Date expiredDate, int expireDays) {
        this.expiredDate = expiredDate;
        this.expireDays = expireDays;
        this.timeZone = timeZone;

        long currentTime = System.currentTimeMillis();
        int zoneOffset =   TimeZone.getTimeZone(timeZone).getOffset(currentTime);
        int defaultOffset = TimeZone.getDefault().getOffset(currentTime);

        /**
         * Example:
         *     TimeZone.getTimeZone(timeZone) is BST
         *     timeZone is CET
         *
         *     There is one hours difference, which is 3600000 milliseconds
         *
         */

        this.differenceInHoursMillis = (zoneOffset - defaultOffset);
    }


    /**
     *
     * Subtract a number of expire days from the date
     *
     * @param dateTimeNow - the date and time now
     * @return - the date and time minus the number of expired days
     *           + (difference in hours for timezone * expiryDays)
     *
     */
    private Date parseDate(Date dateTimeNow) {
        return new Date(dateTimeNow.getTime() - (expireDays * DAY_IN_MS) + (this.differenceInHoursMillis * expireDays));
    }


    private boolean hasDateExpired(Date currentDate) {
        Date dateTimeWithExpire = parseDate(currentDate);

        return dateTimeWithExpire.before(expiredDate);
    }


    public static void main(String[] args) throws ParseException {

        /* Set the expiry date 7 days, and expireDays = 3
        *
        * Not expired
        */

        Calendar cal = Calendar.getInstance();
        cal.add(Calendar.DATE, -7);

        ExpiredDate expired = new ExpiredDate("CET", cal.getTime(), 3);

        Date dateTimeNow = new Date();

        if (expired.hasDateExpired(dateTimeNow)) {
            System.out.println("expired");
        } else {
            System.out.println("NOT expired");

        }

        /* Set the expiry date / time, and expireDays to 2 days
         *  Not expired - because the CET timezone adds two hours to the dateTimeWithExpire
        */


        cal = Calendar.getInstance();
        cal.add(Calendar.DATE, -2);

        expired = new ExpiredDate("CET", cal.getTime(), 2);

        dateTimeNow = new Date();

        if (expired.hasDateExpired(dateTimeNow)) {
            System.out.println("expired");
        } else {
            System.out.println("NOT expired");

        }

        /* Set the expiry date 1 days, and expireDays = 2
        *
        * expired
        */

        cal = Calendar.getInstance();
        cal.add(Calendar.DATE, -1);

        expired = new ExpiredDate("CET", cal.getTime(), 2);

        dateTimeNow = new Date();

        if (expired.hasDateExpired(dateTimeNow)) {
            System.out.println("expired");
        } else {
            System.out.println("NOT expired");

        }

     } 
 }
于 2017-06-01T21:42:56.657 回答