Search code examples
javadateparsingtime

Parse period/duration string literal into Period/Duration instance in Java


I would like to parse any duration/period string literal (in the format shown in the examples below) into a Period/Duration instance in Java.

By duration/period, I mean an amount of time that contains both date-based and time-based quantities. In Java, Duration alone only works for time-based quantities like seconds, minutes, and hours, while Period alone only works for date-based quantities like years, months, and days.

The string to parse won't always contain all quantities.

Examples:

  • 29 days 23 hours 59 minutes 20 seconds
  • 4 years 10 months 10 seconds
  • 10 days 9 minutes
  • 1 month

How would I do that, if possible?


Solution

  • The ThreeTen Extra library seems like a good solution, but in my case I rather not include a large library just for the privilege of using a class that basically is a simple wrapper around java.time.Duration and java.time.Period (though in threeten's case it is probably a wrapper around their versions of Duration and Period - which I encountered and mostly find annoying).

    So I wrote this replacement (here by posted under CC0 and/or whatever SO pretends they can force people to license their copyrighted content under):

    
    import java.time.Duration;
    import java.time.Instant;
    import java.time.Period;
    
    /**
     * Copyright Oded Arbel <oded@geek.co.il>
     * Licensed under [CC0](https://creativecommons.org/public-domain/cc0/)
     */
    public class PeriodDuration {
        public static final ZoneId ZONE_UTC = ZoneId.ofOffset("", ZoneOffset.UTC);
        private Period period;
        private Duration duration;
        
        private PeriodDuration(Period period, Duration duration) {
            this.period = period;
            this.duration = duration;
        }
    
        public static PeriodDuration parse(String iso8601period) {
            var val = iso8601period.toLowerCase().startsWith("p") ? iso8601period.substring(1) : iso8601period;
            var parts = val.split("T",2);
            return new PeriodDuration(
                    parts[0].isEmpty() ? Period.ZERO : Period.parse("P" + parts[0]),
                    parts.length < 2 || parts[1].isEmpty() ? Duration.ZERO : Duration.parse("PT" + parts[1]));
        }
        
        @Override
        public String toString() {
            return period.toString() + "T" + duration.toString().split("T")[1];
        }
        
        public Period getPeriod() {
            return period;
        }
        
        public Duration getDuration() {
            return duration;
        }
        
        public Instant addTo(Instant time) {
            return time.atZone(ZONE_UTC).plus(period).plus(duration).toInstant();
        }
        
        public Instant substractFrom(Instant time) {
            return time.atZone(ZONE_UTC).minus(period).minus(duration).toInstant();
        }
    }
    

    I did not review any ThreeTen code - so this implementation may be missing a lot of features and/or doing things wrong - YMMV.