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

org.dstadler.commons.date.DateParser Maven / Gradle / Ivy

There is a newer version: 1.3.5
Show newest version
package org.dstadler.commons.date;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.apache.commons.lang3.time.FastDateFormat;

import java.text.ParseException;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Modelled after Graphite from-to parameter
 *
 * Note: Passing in absolute time is expected to be stated in the European timezone!
 */
public class DateParser {
    private static final TimeZone TIME_ZONE = TimeZone.getTimeZone("GMT+2");

    private final static Pattern RELATIVE_TIME_PATTERN = Pattern.compile("(\\d+)(s|second|seconds|min|minute|minutes|h|hour|hours|d|day|days|w|week|weeks|mon|month|months|y|year|years)");

    private final static Map UNIT_CONVERSION_TABLE = new HashMap<>();
    static {
        UNIT_CONVERSION_TABLE.put("s", 1);
        UNIT_CONVERSION_TABLE.put("second", 1);
        UNIT_CONVERSION_TABLE.put("seconds", 1);
        UNIT_CONVERSION_TABLE.put("min", 60);
        UNIT_CONVERSION_TABLE.put("minute", 60);
        UNIT_CONVERSION_TABLE.put("minutes", 60);
        UNIT_CONVERSION_TABLE.put("h", 60*60);
        UNIT_CONVERSION_TABLE.put("hour", 60*60);
        UNIT_CONVERSION_TABLE.put("hours", 60*60);
        UNIT_CONVERSION_TABLE.put("d", 24*60*60);
        UNIT_CONVERSION_TABLE.put("day", 24*60*60);
        UNIT_CONVERSION_TABLE.put("days", 24*60*60);
        UNIT_CONVERSION_TABLE.put("w", 7*24*60*60);
        UNIT_CONVERSION_TABLE.put("week", 7*24*60*60);
        UNIT_CONVERSION_TABLE.put("weeks", 7*24*60*60);
        UNIT_CONVERSION_TABLE.put("mon", 30*24*60*60);
        UNIT_CONVERSION_TABLE.put("month", 30*24*60*60);
        UNIT_CONVERSION_TABLE.put("months", 30*24*60*60);
        UNIT_CONVERSION_TABLE.put("y", 365*24*60*60);
        UNIT_CONVERSION_TABLE.put("year", 365*24*60*60);
        UNIT_CONVERSION_TABLE.put("years", 365*24*60*60);
    }

    private static final FastDateFormat[] DATE_PARSERS = new FastDateFormat[] {
        // note: the order here is important to not parse things incorrectly!
        // SimpleDateFormat will use a fairly matching format as well, so we
        // need to put the most complex formats to the front to avoid wrong parsing.
        FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss.S" /* Z */, TIME_ZONE, Locale.GERMANY),
        FastDateFormat.getInstance("yyyy-MM-dd", TIME_ZONE),
        FastDateFormat.getInstance("yyyyMMdd", TIME_ZONE),
        FastDateFormat.getInstance("HH:mm yyyyMMdd", TIME_ZONE),
        FastDateFormat.getInstance("HH:mm yyMMdd", TIME_ZONE),
        FastDateFormat.getInstance("MM/dd/yy", TIME_ZONE),
    };

    public final static long ONE_SECOND = 1000;
    public final static long ONE_MINUTE = 60 * ONE_SECOND;
    public final static long ONE_HOUR = 60 * ONE_MINUTE;
    public final static long ONE_DAY = 24 * ONE_HOUR;
    public final static long ONE_WEEK = 7 * ONE_DAY;

    /* TODO: things that Graphite can parse in addition:

    &from=04:00_20110501&until=16:00_20110501
    (shows 4AM-4PM on May 1st, 2011)

    &from=20091201&until=20091231
    (shows December 2009)

    &from=noon+yesterday
    (shows data since 12:00pm on the previous day)

    &from=6pm+today
    (shows data since 6:00pm on the same day)

    &from=january+1
    (shows data since the beginning of the current year)

    &from=monday
    (show data since the previous monday)

    */

    public static Date parseURLDate(String dateStr, Date defaultDate) {
        if(StringUtils.isEmpty(dateStr)) {
            return defaultDate;
        }

        /*
        There are multiple formats for these functions:

        &from=-RELATIVE_TIME
        &from=ABSOLUTE_TIME

        &from and &until can mix absolute and relative time if desired.
        */
        if(dateStr.startsWith("-")) {
            /*
            RELATIVE_TIME is a length of time since the current time. It is always preceded my a minus sign ( - ) and follow by a unit of time. Valid units of time:
            Abbreviation     Unit
            s     Seconds
            min     Minutes
            h     Hours
            d     Days
            w     Weeks
            mon     30 Days (month)
            y     365 Days (year)
             */

            // cut off leading minus-sign and parse value and unit
            String parse = dateStr.substring(1).toLowerCase();
            Matcher matcher = RELATIVE_TIME_PATTERN.matcher(parse);
            if(!matcher.matches()) {
                throw new IllegalArgumentException("Could not parse relative time value " + parse + ", expected to match " + RELATIVE_TIME_PATTERN);
            }

            String value = matcher.group(1);
            String unit = matcher.group(2);

            // check adjustment factor for this unit
            Integer factor = UNIT_CONVERSION_TABLE.get(unit);
            if(factor == null) {
                throw new IllegalArgumentException("Unknown unit: " + unit + " found while parsing relative time: " + parse);
            }

            // finally use the factor to calculate the resulting previous point in time
            return DateUtils.addSeconds(new Date(), (-1)*Integer.parseInt(value)*factor);
        }

/*
        ABSOLUTE_TIME is in the format HH:MM_YYMMDD, YYYYMMDD, MM/DD/YY, or any other at(1)-compatible time format.
        Abbreviation     Meaning
        HH     Hours, in 24h clock format. Times before 12PM must include leading zeroes.
        MM     Minutes
        YYYY     4 Digit Year.
        MM     Numeric month representation with leading zero
        DD     Day of month with leading zero
 */

        for(FastDateFormat format : DATE_PARSERS) {
            try {
                return format.parse(dateStr);
            } catch (ParseException e) {
                // expected here if the format does not match
            }
        }

        StringBuilder string = new StringBuilder();
        for(FastDateFormat format : DATE_PARSERS) {
            string.append(format.getPattern()).append(", ");
        }
        throw new IllegalArgumentException("Could not parse absolute date " + dateStr + " via any of the available parsers: " + string);
    }

    /**
     * Takes the time in milliseconds since the epoch and
     * converts it into a string of "x days/hours/minutes/seconds"
     * compared to the current time.
     *
     * @param ts The timestamp in milliseconds since the epoch
     * @param suffix Some text that is appended only if there is a time-difference, i.e.
     *     it is not appended when the time is now.
     *
     * @return A readable string with a short description of how long ago the ts was.
     */
    public static String computeTimeAgoString(long ts, String suffix) {
        long now = System.currentTimeMillis();

        checkArgument(ts <= now, "Cannot handle timestamp in the future" +
                ", now: " + now + "/" + new Date(now) +
                ", ts: " + ts + "/" + new Date(ts));

        long diff = now - ts;
        return timeToReadable(diff, suffix);
    }

    /**
     * Format the given number of milliseconds as readable string.
     *
     * @param millis The number of milliseconds to print.
     *
     * @return The readable string.
     */
    public static String timeToReadable(long millis) {
        return timeToReadable(millis, "");
    }

    /**
     * Format the given number of milliseconds as readable string, optionally
     * appending a suffix.
     *
     * @param millis The number of milliseconds to print.
     * @param suffix A suffix that is appended if the millis is > 0, specify "" if not needed.
     *
     * @return The readable string
     */
    public static String timeToReadable(long millis, String suffix) {
		if (millis == 0) {
			return "0 s" + suffix;
		}

        StringBuilder builder = new StringBuilder();
        if (millis < 0) {
            builder.append("-");

            // border-case: avoid overflow as abs(MIN_VALUE) is higher than MAX_VALUE!
            if (millis == Long.MIN_VALUE) {
                millis = Long.MAX_VALUE;
            } else {
                millis *= -1;
            }
        }

		boolean haveDays = false;
        if(millis >= ONE_WEEK) {
            millis = handleTime(builder, millis, ONE_WEEK, "week", "s");
            haveDays = true;
        }

        if(millis >= ONE_DAY) {
            millis = handleTime(builder, millis, ONE_DAY, "day", "s");
            haveDays = true;
        }

        boolean haveHours = false;
        if(millis >= ONE_HOUR) {
            millis = handleTime(builder, millis, ONE_HOUR, "h", "");
            haveHours = true;
        }

        boolean haveMinutes = false;
        if((!haveDays || !haveHours) && millis >= ONE_MINUTE) {
            millis = handleTime(builder, millis, ONE_MINUTE, "min", "");
            haveMinutes = true;
        }

        if(!haveDays && !haveHours && millis >= ONE_SECOND) {
            /*millis =*/ handleTime(builder, millis, ONE_SECOND, "s", "");
        } else if (!haveDays && !haveHours && !haveMinutes) {
            handleTime(builder, millis, 1, "ms", "");
        }

        if(builder.length() > 0) {
            // cut off trailing ", "
            builder.setLength(builder.length() - 2);
            builder.append(suffix);
        }

        return builder.toString();
    }

    private static long handleTime(StringBuilder builder, long diff, long amount, String type, String plural) {
        long days = diff / amount;
        builder.append(days).append(" ").append(type).append(days > 1 ? plural : "").append(", ");
        diff -= days*amount;
        return diff;
    }

    public static void checkArgument(boolean expression, Object errorMessage) {
        if (!expression) {
            throw new IllegalArgumentException(String.valueOf(errorMessage));
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy