How to convert ISO 8601 period to a string readable by humans [Android Studio]?
Asked Answered
U

3

18

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!

Ultranationalism answered 14/1, 2019 at 18:59 Comment(3)
This stackoverflow thread will help.Physiological
It seems like this will help you. programcreek.com/java-api-examples/… That class is made to handle the periods you seemingly have. With the parse() method you can interpret the strings.Triptych
You can copy part of Period class source code, look for parse() methodPassable
G
23

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

Gramarye answered 14/1, 2019 at 20:9 Comment(2)
Thanks a lot! I have decided to use this solution for now!Ultranationalism
I strongly recommend this answer instead of using RegEx as suggested in other answers. It's hard to guess what made someone DV this answer.Phosphoresce
J
12

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;
}
Johannesburg answered 14/1, 2019 at 20:19 Comment(1)
Not only re-inventing the wheel but you added a regular expression! Way to go regex FTWParallelepiped
L
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");
        }
    }
Lesialesion answered 4/2, 2021 at 8:29 Comment(1)
For an ISO string of days, hours, minutes and seconds (with or without fraction of second), also do not reinvent the wheel. The Duration class from java.time parses such a string out of the box. Duration.parse(duration).toSeconds() (if you do need to convert to seconds for some reason).Gramarye

© 2022 - 2024 — McMap. All rights reserved.