将 org.joda.time.Period 转换为 java.time.Period

5

我正在尝试用java.time替换org.joda.time.Period。

我们在数据库中存储以下值:

P1M, P1Y, P1D, PT1H, PT1M

仅需解析此值:

Period monthly = ISOPeriodFormat.standard().parsePeriod(<One of the above value from DB>);

这很简单,而且可以按预期获取时间段。但是,现在使用java.time替换会很麻烦。

因为,P1D, P1M, P1Y应该使用以下代码进行解析:

java.time.Period period = java.time.Period.parse("P1M");

而且,应该使用以下代码解析P1H和P1D

Duration dailyD = Duration.parse("P1D");

所以,我可能还需要一些字符串检查,例如:
if(value.startsWith("PT")) {
   // Use java.time.Duration
} else {
   // Use java.time.Period
}

这个逻辑有没有更好的代码实现?

另外,最终,我需要根据上述 java.time.Period 或 java.time.Duration 找到从某个 startTime 到现在日期的事件次数。

比如,如果 startDateTime 是 2015年1月1日08:30

LocalDateTime startDateTime = // the above startDateTime ..

    if(value.startsWith("PT")) {
       // Use java.time.Duration
     ChronoUnit.SECONDS.between(startDateTime,curentDate)/(duration.toMillis()/1000)
    } else {

 if(value.endsWith("Y")) {
       // Use java.time.Period
ChronoUnit.YEARS.between(startDateTime,curentDate)/(period.getYears()/1000)
} else if(value.endsWith("M")){
 ChronoUnit.MONTHS.between(startDateTime,curentDate)/(period.getMonths()/1000)
}

有没有其他更好的方法来避免这种值解析?

我的输入可能为P2M、P10M、P2Y、PT15M、PT10M。它不会像P1MT10M这样同时包含Period和时间的组合。但是可能有任意数量的月份、年份或天数。


这对您有用吗? https://github.com/meetup/timeywimey/blob/master/timeywimey-core/src/main/java/com/meetup/timeywimey/JodaConverters.java - Lefteris Bab
我认为你在上一个代码片段中最后两次除以1000的时候是不想这样做的,其中包括年份和月份。 - Ole V.V.
你可能想要查看ThreeTen Extra项目中的org.threeten.extra.PeriodDuration - Ole V.V.
1
我认为(和你一样?)我更喜欢将所有解析工作留给库方法。一个选项是尝试将其解析为“Period”和“Duration”,并选择有效的那个。 - Ole V.V.
1
你确定你的时间段始终是一个“Period”或“Duration”吗?或者反过来说,你的数据中可能会出现类似于“P1MT1H”的东西吗? - Ole V.V.
如果您解析为 java.time.Duration,那么原始信息(例如一天或86400秒)将丢失,因为该类型仅存储秒和纳秒。关于 java.time.Period,它会自动将一周转换为七天,这可能不是您想要的。在我看来,我会避免转换,因为与 JodaTime 相比,java.time 类型在这里受到了太多限制。 - Meno Hochschild
1个回答

3

Java-8没有像org.joda.time.Period类那样复杂的持续时间类型。但是,您可以以简单直接的方式创建自己的TemporalAmount接口实现:

import java.time.DateTimeException;    
import java.time.temporal.ChronoUnit;
import java.time.temporal.Temporal;
import java.time.temporal.TemporalAmount;
import java.time.temporal.TemporalUnit;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import static java.time.temporal.ChronoUnit.*;

public class SimpleDuration implements TemporalAmount {

    private static final List<TemporalUnit> SUPPORTED_UNITS =
        Collections.unmodifiableList(Arrays.asList(YEARS, MONTHS, DAYS, HOURS, MINUTES));

    private final int amount;
    private final ChronoUnit unit;

    private SimpleDuration(int amount, ChronoUnit unit) {
        this.amount = amount;
        this.unit = unit;
    }

    public static SimpleDuration of(int amount, ChronoUnit unit) {
        if (SUPPORTED_UNITS.contains(unit)) {
            return new SimpleDuration(amount, unit);
        } else {
            throw new IllegalArgumentException("Not supported: " + unit);
        }
    }

    @Override
    public long get(TemporalUnit unit) {
        if (this.unit.equals(unit)) {
          return this.amount;
        }
        return 0;
    }

    @Override
    public List<TemporalUnit> getUnits() {
        return SUPPORTED_UNITS;
    }

    @Override
    public Temporal addTo(Temporal temporal) {
        return temporal.plus(this.amount, this.unit);
    }

    @Override
    public Temporal subtractFrom(Temporal temporal) {
        return temporal.minus(this.amount, this.unit);
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof SimpleDuration) {
            SimpleDuration that = (SimpleDuration) obj;
            return this.unit == that.unit && this.amount == that.amount;
        } else {
            return false;
        }
    }

    @Override
    public int hashCode() {
        return this.unit.hashCode() + 37 * Integer.hashCode(this.amount);
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        if (this.amount < 0) {
            sb.append('-');
        }
        sb.append('P');
        if (this.unit.isTimeBased()) {
            sb.append('T');
        }
        sb.append(Math.abs(this.amount));
        switch (this.unit) {

            case YEARS :
                sb.append('Y');
                break;

            case MONTHS :
                sb.append('M');
                break;

            case DAYS :
                sb.append('D');
                break;

            case HOURS :
                sb.append('H');
                break;

            case MINUTES :
                sb.append('M');
                break;

            default :
                throw new UnsupportedOperationException(this.unit.name());
        }
        return sb.toString();
    }

    public static SimpleDuration parse(String input) {
        int len = input.length();
        int index = 0;
        boolean negative = false;
        if (len > 0 && input.charAt(0) == '-') {
            negative = true; // for XML-Schema (not for ISO-8601)
            index++;
        }
        if (len >= 3 && input.charAt(index) == 'P') {
            boolean timeBased = (input.charAt(index + 1) == 'T');
            char last = input.charAt(len - 1);
            ChronoUnit unit;
            switch (last) {

                case 'Y' :
                    unit = YEARS;
                    break;

                case 'M' :
                    unit = (timeBased ? MINUTES : MONTHS);
                    break;

                case 'D' :
                    unit = DAYS;
                    break;

                case 'H' :
                    unit = HOURS;
                    break;

                default :
                    throw new DateTimeException(
                        "Unknown unit symbol found at last position: " + input
                    );
            }
            if (unit.isTimeBased() != timeBased) {
                throw new DateTimeException("Invalid XML-Schema-format: " + input);
            }
            try {
              int amount =
                Integer.parseInt(
                  input.substring(index).substring(timeBased ? 2 : 1, len - 1 - index));
              if (amount < 0) {
                throw new DateTimeException(
                  "XML-Schema does not allow negative amounts inside: " + input);
              }
              return SimpleDuration.of(negative ? -amount : amount, unit);
            } catch (NumberFormatException ex) {
                throw new DateTimeException("Invalid XML-Schema-format: " + input, ex);
            }
        }
        throw new DateTimeException("Cannot parse: " + input);
    }

    public static void main(String... args) {
        System.out.println(parse("-PT10M")); // -PT10M
    }
}

谢谢您的回复。但是,我的输入可能是P2M、P10M、P2Y、PT15M、PT10M,而不会像P1MT10M这样同时包含Period和time。不过可以有任意数量的月份、年份或天数。 - user1578872
@user1578872 噢,好的,这与您原来的问题中所告诉我们的有所不同。我将很快根据您的新要求调整我的答案。 - Meno Hochschild
@user1578872,我已经调整了我的答案。建议的代码涵盖了适用于您提到的表示形式的简化XML模式,包括负持续时间可能有前导减号的情况。 - Meno Hochschild
太棒了。希望可以用RuntimeException替换ChronoException,因为我不想导入net.time4j.engine。 - user1578872
2
@assylias Threeten-extraPeriodDuration与XML模式不同(在负持续时间的情况下不适用),而net.time4j.Duration对于负持续时间具有不同的算术行为,并具有额外的格式化功能(在threeten-extra中缺失),因此我们在这里谈论的是苹果和橙子,不能真正比较或交换表示时间量的类。而且OP明确要求Java-8解决方案,而不是第三方库。 - Meno Hochschild
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接