【问题标题】:java.time: Compare two Instants - get the number of hours, minutes, seconds, years, months between the two [duplicate]java.time:比较两个 Instants - 获取两者之间的小时数,分钟数,秒数,年数,月数
【发布时间】:2019-11-06 18:10:03
【问题描述】:

我试过这段代码:

public class TimePassed {
    private long seconds;
    private long minutes;
    private long hours;
    private long days;
    private long years;
    ...

    public TimePassed(double unixSeconds)  {
        Instant now = Instant.now();
        Instant ago = Instant.ofEpochSecond((long) unixSeconds);

        this.seconds = ChronoUnit.SECONDS.between(
            ago.atZone(ZoneId.systemDefault()),
            now.atZone(ZoneId.systemDefault()));  //6100
        this.minutes = ChronoUnit.MINUTES.between(
            ago.atZone(ZoneId.systemDefault()),
            now.atZone(ZoneId.systemDefault()));  //101
        this.hours = ChronoUnit.HOURS.between(
            ago.atZone(ZoneId.systemDefault()),
            now.atZone(ZoneId.systemDefault()));  //1
        this.days = ChronoUnit.DAYS.between(
            ago.atZone(ZoneId.systemDefault()),
            now.atZone(ZoneId.systemDefault()));  //0
        this.years = ChronoUnit.YEARS.between(
            ago.atZone(ZoneId.systemDefault()),
            now.atZone(ZoneId.systemDefault()));  //0
    }
}

然后TimePassed 对象将具有seconds = 6100minutes = 101hours = 1,而我希望它是hours = 1minutes = 41, seconds = 40,以便60*60 + 41*60 + 40 = 6100。可以使用java.time 包吗?因为到目前为止,我只能通过秒、通过分钟或通过小时等。两者都不会考虑另一个。

【问题讨论】:

    标签: java datetime unix-timestamp


    【解决方案1】:

    Java 9 答案:Duration.toXxxPart 方法

    基本思路,不完整:

        Duration dur = Duration.between(ago, now);
    
        this.seconds = dur.toSecondsPart(); // 40
        this.minutes = dur.toMinutesPart(); // 41
        this.hours = dur.toHoursPart(); // 1
        this.days = dur.toDaysPart(); // 0
    

    使用与问题相距 6100 秒的瞬间进行测试。 toXxxPart 方法是在 Java 9 中引入的。对于 Java 8(或 ThreeTen Backport),您需要从较粗的单位开始,即天,然后从持续时间中减去它们,然后再获得下一个更精细的单位。示例见this answer by lauhub

    不过,要完全正确,年份和日子有点棘手。要仅获取超过整年的天数,请查看完整代码:

        ZoneId zone = ZoneId.systemDefault();
        ZonedDateTime agoZdt = ago.atZone(zone);
        ZonedDateTime nowZdt = now.atZone(zone);
        this.years = ChronoUnit.YEARS.between(agoZdt, nowZdt);
        ZonedDateTime afterWholeYears = agoZdt.plusYears(this.years);
    
        Duration dur = Duration.between(afterWholeYears, nowZdt);
    
        this.seconds = dur.toSecondsPart(); // 40
        this.minutes = dur.toMinutesPart(); // 41
        this.hours = dur.toHoursPart(); // 1
        this.days = dur.toDays(); // 0
    

    我特意只阅读ZoneId.systemDefault() 一次,只是为了万一有人正在更改默认时区设置。

    【讨论】:

    • 它不能按我需要的方式工作。例如,它输出seconds = 118minutes = 1,而我需要它来执行minutes = 1; seconds = 58
    • 我没有看到duration.toHoursPart 方法,只有duraction.toHours,根本没有duration.toSeconds。编辑:哦 java 9
    • 我编辑了 Java 8 的简短说明。@parsecer
    【解决方案2】:

    这样的事情应该可以工作:

    ZoneId zone = ZoneId.systemDefault();
    
    ZonedDateTime ago = ZonedDateTime.ofInstant(Instant.ofEpochSeconds(unixSeconds), zone);
    ZonedDateTime now = ZonedDateTime.now(zone);
    
    Period period = Period.between(ago.toLocalDate(), now.toLocalDate());
    
    ZonedDateTime adjusted = ago.with(now.toLocalDate());
    if (adjusted.isAfter(now)) {
        adjusted = adjusted.minusDays(1);
        period = period.minusDays(1);
    }    
    
    Duration duration = Duration.between(adjusted, now);
    assert duration.toDaysPart() == 0;
    
    years   = period.getYears();
    months  = period.getMonths();
    days    = period.getDays();
    hours   = duration.toHoursPart();
    minutes = duration.toMinutesPart();
    seconds = duration.toSecondsPart();
    

    为什么/如何工作:

    1. 非时间字段(年、月、日)的差异是通过使用专门用于此目的的专用Period 类型计算的。它使用两个日期时间的LocalDate 部分,这是安全的,因为它们都在同一个时区
    2. 要知道其余字段的差异,我们调整“ago”值,使天数完全相同。如果我们碰巧过冲(如果“以前”发生在比“现在”更早的当地时间,则可能会发生这种情况),我们会通过将调整后的日期时间和时间段都减少一天来进行调整
    3. 然后,我们使用Duration 类来获取基于时间的字段之间的差异。由于我们在那里查询调整后的日期时间之间的差异,因此我们的持续时间不会超过一天,我为此添加了断言。
    4. 最后,我使用了PeriodDuration 上可用的各种方法来获取它们的“字段”。请注意,我使用的 Duration 类的方法仅在 Java 9 之后才可用,因此如果您还没有它,则必须使用 toMinutes() 之类的方法并手动将它们除以分钟数等每小时等:
        // those constants you'll have to define on your own, shouldn't be hard
        hours = duration.toHours() % Constants.HOURS_PER_DAY;
    

    或者,如果您不想定义常量,您可以通过调整“ago”变量来重复这个技巧:

    adjusted = ago.with(now.toLocalDate());
    if (adjusted.isAfter(now)) {
        adjusted = adjusted.minusDays(1);
        period = period.minusDays(1);
    }
    hours = HOURS.between(adjusted, now);
    adjusted = adjusted.withHour(now.getHour());
    if (adjusted.isAfter(now)) {
        adjusted = adjusted.minusHour(1);
        hours -= 1;
    }
    
    minutes = MINUTES.between(adjusted, now);
    adjusted = adjusted.withMinute(now.getMinute());
    if (adjusted.isAfter(now)) {
        adjusted = adjusted.minusMinutes(1);
        minutes -= 1;
    }
    seconds = SECONDS.between(adjusted, now);
    

    【讨论】:

    • 我没有看到duration.toHoursPart 方法,只有duraction.toHours,根本没有duration.toSeconds
    • @parsecer,正如我所提到的,这些已添加到 since Java 9 类中,如果您仍在使用 Java 8,则必须编写胶水代码。
    • 很好且解释清楚的解决方案。作为一个细节,我不一定相信你的assert 在所有极端情况下都成立。当夏令时 (DST) 结束时,一天可以是 25 小时。所以我认为你的Duration 可能会结束,例如 24 小时 30 分钟,Duration 将解释为 1 天 30 分钟。
    • @OleV.V.,这可能是真的,我想过这个东西在接近转换时会如何表现,但还没有测试过。如果你说的是真的,那么断言是错误的,但是我不知道该给出哪个答案:处于过渡中是否意味着我们应该报告两个小时过去了,而大多数情况下它只是一个小时?编辑:据我所知,这些情况下的持续时间说“一小时过去了”,这意味着史蒂文认为我们只是凡人,而且断言毕竟可能没有错。
    • 更新:当我说“Steven”时,我的意思是“Stephen”,我的错。
    猜你喜欢
    • 2017-05-03
    • 2012-12-03
    • 2016-11-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-09-10
    • 1970-01-01
    相关资源
    最近更新 更多