16

Any suggestions on how to convert the ISO 8601 duration format PnYnMnDTnHnMnS (ex: P1W, P5D, P3D) to number of days?

I'm trying to set the text of a button in a way that the days of free trial are displayed to the user.

Google provides the billing information in the ISO 8601 duration format through the key "freeTrialPeriod", but I need it in numbers the user can actually read.

The current minimum API level of the app is 18, so the Duration and Period classes from Java 8 won't help, since they are meant for APIs equal or greater than 26.

I have set the following method as a workaround, but it doesn't look like the best solution:

private String getTrialPeriodMessage() {
        String period = "";

        try {
            period = subsInfoObjects.get(SUBS_PRODUCT_ID).getString("freeTrialPeriod");
        } catch (Exception e) {
            e.printStackTrace();
        }

        switch (period) {
            case "P1W":
                period = "7";
                break;
            case "P2W":
                period = "14";
                break;
            case "P3W":
                period = "21";
                break;
            case "P4W":
                period = "28";
                break;
            case "P7D":
                period = "7";
                break;
            case "P6D":
                period = "6";
                break;
            case "P5D":
                period = "5";
                break;
            case "P4D":
                period = "4";
                break;
            case "P3D":
                period = "3";
                break;
            case "P2D":
                period = "2";
                break;
            case "P1D":
                period = "1";
        }

        return getString(R.string.continue_with_free_trial, period);
    }

Any suggestions on how to improve it?

Thanks!

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
  • This [stackoverflow](https://stackoverflow.com/questions/2201925/converting-iso-8601-compliant-string-to-java-util-date) thread will help. – Ugnes Jan 14 '19 at 19:02
  • It seems like this will help you. https://www.programcreek.com/java-api-examples/index.php?source_dir=OG-Commons-master/modules/basics/src/main/java/com/opengamma/basics/schedule/Frequency.java That class is made to handle the periods you seemingly have. With the parse() method you can interpret the strings. – Tschallacka Jan 14 '19 at 19:04
  • You can copy part of [Period class source code](https://android.googlesource.com/platform/libcore/+/refs/heads/master/ojluni/src/main/java/java/time/Period.java#322), look for `parse()` method – Sergey Glotov Jan 14 '19 at 19:40

3 Answers3

21

java.time and ThreeTenABP

This exists. Consider not reinventing the wheel.

import org.threeten.bp.Period;

public class FormatPeriod {

    public static void main(String[] args) {
        String freeTrialString = "P3W";
        Period freeTrial = Period.parse(freeTrialString);
        String formattedPeriod = "" + freeTrial.getDays() + " days";
        System.out.println(formattedPeriod);
    }
}

This program outputs

21 days

You will want to add a check that the years and months are 0, or print them out too if they aren’t. Use the getYears and getMonths methods of the Period class.

As you can see, weeks are automatically converted to days. The Period class doesn’t represent weeks internally, only years, months and days. All of your example strings are supported. You can parse P1Y (1 year), P1Y6M(1 year 6 months), P1Y2M14D (1 year 2 months 14 days), even P1Y5D, P2M, P1M15D, P35D and of course P3W, etc.

Question: Can I use java.time on Android?

Yes, java.time works nicely on older and newer Android devices. It just requires at least Java 6.

  • In Java 8 and later and on newer Android devices (from API level 26) the modern API comes built-in. In this case import from java.time rather than org.threeten.bp.
  • In Java 6 and 7 get the ThreeTen Backport, the backport of the new classes (ThreeTen for JSR 310; see the links at the bottom).
  • On (older) Android use the Android edition of ThreeTen Backport. It’s called ThreeTenABP. And make sure you import the date and time classes from org.threeten.bp with subpackages.

Links

Ole V.V.
  • 81,772
  • 15
  • 137
  • 161
11

If you do want to re-invent the wheel here is a simple solution to parse ISO 8601 durations, it only supports year/week/day and not the time part but it can of course be added. Another limitation of this solution is that it expects the different types, if present, comes in the order Year->Week->Day

private static final String REGEX = "^P((\\d)*Y)?((\\d)*W)?((\\d)*D)?";

public static int parseDuration(String duration) {            
    int days = 0;

    Pattern pattern = Pattern.compile(REGEX);
    Matcher matcher = pattern.matcher(duration);

    while (matcher.find()) {

        if (matcher.group(1) != null ) {
            days += 365 * Integer.valueOf(matcher.group(2));
        }
        if (matcher.group(3) != null ) {
            days += 7 * Integer.valueOf(matcher.group(4));
        }
        if (matcher.group(5) != null ) {
            days +=  Integer.valueOf(matcher.group(6));
        }

    }
    return days;
}
Joakim Danielson
  • 43,251
  • 5
  • 22
  • 52
0

I needed to convert to seconds,

so, I am Looking at

@Joakim Danielson's

answer,

I improved the regular expression slightly. Please refer to those who need it.

private int changeIso8601TimeIntervalsToSecond(String duration) throws Exception {

        //P#DT#H#M#S
        String REGEX = "^P((\\d*)D)?T((\\d*)H)?((\\d*)M)?((\\d*)S)?$";

        Pattern pattern = Pattern.compile(REGEX);
        Matcher matcher = pattern.matcher(duration);

        int seconds = 0;
        boolean didFind = false;

        while(matcher.find()){

            didFind = true;

            //D
            if(matcher.group(1) != null){
               seconds += 60 * 60 * 24 * Integer.parseInt(Objects.requireNonNull(matcher.group(2)));
            }

            //H
            if(matcher.group(3) != null){
                seconds += 60 * 60 * Integer.parseInt(Objects.requireNonNull(matcher.group(4)));
            }

            //M
            if(matcher.group(5) != null){
                seconds += 60 * Integer.parseInt(Objects.requireNonNull(matcher.group(6)));
            }

            //S
            if(matcher.group(7) != null){
                seconds += Integer.parseInt(Objects.requireNonNull(matcher.group(8)));
            }
        }

        if(didFind){
            return seconds;
        }else{
            throw new Exception("NoRegularExpressionFoundException");
        }
    }
unemployer
  • 158
  • 7