All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.lindstrom.mpd.support.DurationDeserializer Maven / Gradle / Ivy

There is a newer version: 0.13
Show newest version
package io.lindstrom.mpd.support;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;

import java.io.IOException;
import java.time.Duration;
import java.time.format.DateTimeParseException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class DurationDeserializer extends JsonDeserializer {
    private static final int HOURS_PER_YEAR = 8766;
    private static final int MINUTES_PER_MONTH = 43830;
    private static final int HOURS_PER_DAY = 24;

    // PnYnMnDTnHnMnS
    private static final Pattern PATTERN =
            Pattern.compile("([-+]?)P" +
                            "(?:([-+]?[0-9]+)Y)?" +
                            "(?:([-+]?[0-9]+)M)?" +
                            "(?:([-+]?[0-9]+)D)?" +
                            "(T(?:([-+]?[0-9]+)H)?(?:([-+]?[0-9]+)M)?(?:([-+]?[0-9]+)(?:[.,]([0-9]{0,12}))?S)?)?",
                    Pattern.CASE_INSENSITIVE);

    private static Duration estimateDuration(long years, long months, long days) {
        if (years > 0 || months > 0 || days > 0) {
            return Duration.ofHours(HOURS_PER_YEAR * years)
                    .plusMinutes(MINUTES_PER_MONTH * months)
                    .plusHours(HOURS_PER_DAY * days);
        } else {
            return Duration.ZERO;
        }
    }

    private static boolean isNegative(CharSequence text, int start, int end) {
        return (start >= 0 && end == start + 1 && text.charAt(start) == '-');
    }

    static long parseFraction(CharSequence text, int start, int end, boolean negate) {
        // regex limits to [0-9]{0,12}
        if (start < 0 || end < 0 || end - start == 0) {
            return 0;
        }

        // Duration only handle nano second resolution but xs:duration allows 12 decimals.
        if (end - start > 9) {
            end = start + 9;
        }

        long fraction = Long.parseLong(text, start, end, 10);

        // for number strings smaller than 9 digits, interpret as if there were trailing zeros
        for (int i = end - start; i < 9; i++) {
            fraction *= 10;
        }

        if (negate) {
            return -fraction;
        } else {
            return fraction;
        }
    }


    private Duration parse(String text) {
        Matcher matcher = PATTERN.matcher(text);
        if (!matcher.matches()) {
            throw new DateTimeParseException("Text cannot be parsed to a Duration", text, 0);
        }

        boolean negate = isNegative(text, matcher.start(1), matcher.end(1));
        int yearStart = matcher.start(2), yearEnd = matcher.end(2);
        int monthStart = matcher.start(3), monthEnd = matcher.end(3);
        int dayStart = matcher.start(4), dayEnd = matcher.end(4);
        int hourStart = matcher.start(6), hourEnd = matcher.end(6);
        int minuteStart = matcher.start(7), minuteEnd = matcher.end(7);
        int secondStart = matcher.start(8), secondEnd = matcher.end(8);
        int fractionStart = matcher.start(9), fractionEnd = matcher.end(9);

        int years = 0;
        int months = 0;
        int days = 0;
        long hoursAsSecs = 0;
        long minutesAsSecs = 0;
        long seconds = 0;
        long nanos = 0;

        if (yearStart >= 0) {
            years = Integer.parseInt(text, yearStart, yearEnd, 10);
        }

        if (monthStart >= 0) {
            months = Integer.parseInt(text, monthStart, monthEnd, 10);
        }

        if (dayStart >= 0) {
            days = Integer.parseInt(text, dayStart, dayEnd, 10);
        }

        if (hourStart >= 0) {
            hoursAsSecs = Math.multiplyExact(3600L, Integer.parseInt(text, hourStart, hourEnd, 10));
        }

        if (minuteStart >= 0) {
            minutesAsSecs = Math.multiplyExact(60L, Integer.parseInt(text, minuteStart, minuteEnd, 10));
        }

        if (secondStart >= 0) {
            seconds = Integer.parseInt(text, secondStart, secondEnd, 10);
            boolean negativeSecs = text.charAt(secondStart) == '-';
            nanos = parseFraction(text, fractionStart, fractionEnd, negativeSecs);
        }

        long secondsTotal = Math.addExact(hoursAsSecs, Math.addExact(minutesAsSecs, seconds));

        Duration duration = Duration.ofSeconds(secondsTotal, nanos).plus(estimateDuration(years, months, days));

        if (negate) {
            return duration.negated();
        }

        return duration;
    }


    @Override
    public Duration deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
        String text = p.getText();

        try {
            return parse(text);
        } catch (Exception e) {
            ctxt.reportWrongTokenException(this, p.currentToken(), "Text cannot be parsed to a Duration");
        }

        return null;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy