• Michael A. Goulet's avatar
    Explicitly set Locale.ENGLISH in DateTimeUtils · 8b85c48f
    Michael A. Goulet authored
    Some tests were failing on my box because I've got my locale set to
    Portuguese; this shouldn't be an issue, since the NanoDB tests are
    explicitly written in English. In order to fix Date parsing language-
    agnostically, I just explicitly set the locale to `Locale.ENGLISH`.
    8b85c48f
DateTimeUtils.java 7.75 KB
package edu.caltech.nanodb.expressions;


import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.Period;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.time.temporal.Temporal;
import java.time.temporal.TemporalAmount;
import java.util.regex.Pattern;
import java.util.Locale;


/**
 * Provides a number of utility operations for working with dates, times and
 * timestamps.
 */
public class DateTimeUtils {
    /**
     * An array of date format specifications that NanoDB recognizes.  New
     * formats can be added to this array to expand what NanoDB is able to
     * parse.
     */
    private static final DateTimeFormatter DATE_FORMATS[] = {
        DateTimeFormatter.ISO_LOCAL_DATE,
        DateTimeFormatter.BASIC_ISO_DATE,
        DateTimeFormatter.ofPattern("dd MMM yyyy", Locale.ENGLISH),
        DateTimeFormatter.ofPattern("MMM dd yyyy", Locale.ENGLISH)
    };

    /**
     * An array of time format specifications that NanoDB recognizes.  New
     * formats can be added to this array to expand what NanoDB is able to
     * parse.
     */
    private static final DateTimeFormatter TIME_FORMATS[] = {
        DateTimeFormatter.ISO_LOCAL_TIME,
        DateTimeFormatter.ofPattern("h:mm[:ss[.SSS]][ ][a]") //,
//        DateTimeFormatter.ofPattern("h:mm[:ss[.SSS]][a]")
    };

    /**
     * An array of date/time format specifications that NanoDB recognizes.
     * New formats can be added to this array to expand what NanoDB is able to
     * parse.
     */
    private static final DateTimeFormatter DATETIME_FORMATS[] = {
        DateTimeFormatter.ISO_LOCAL_DATE_TIME
    };


    /*
    private static final Pattern INTERVAL_FORMATS[] = {
        Pattern.compile("\\d+(\\.\\d+)?\\h+\\D\\w*")
    };
    */


    /**
     * Attempts to parse a string into a date/time value using the formats
     * specified in {@link #DATETIME_FORMATS}.
     *
     * @param s the string to attempt to parse into a date/time
     *
     * @return an object holding the date/time value
     *
     * @throws DateTimeParseException if the string couldn't be parsed into a
     *         date/time value.
     */
    public static LocalDateTime parseDateTime(String s) {
        for (DateTimeFormatter fmt : DATETIME_FORMATS) {
            try {
                return fmt.parse(s, LocalDateTime::from);
            }
            catch (DateTimeParseException e) {
                // That didn't work.  Go on to the next one.
            }
        }

        throw new DateTimeParseException("Could not parse into LocalDateTime",
            s, 0);
    }


    /**
     * Attempts to parse a string into a date value using the formats
     * specified in {@link #DATE_FORMATS}.
     *
     * @param s the string to attempt to parse into a date
     *
     * @return an object holding the date value
     *
     * @throws DateTimeParseException if the string couldn't be parsed into a
     *         date value.
     */
    public static LocalDate parseDate(String s) {
        for (DateTimeFormatter fmt : DATE_FORMATS) {
            try {
                return fmt.parse(s, LocalDate::from);
            }
            catch (DateTimeParseException e) {
                // That didn't work.  Go on to the next one.
            }
        }

        throw new DateTimeParseException("Could not parse into LocalDate",
            s, 0);
    }


    /**
     * Attempts to parse a string into a time value using the formats
     * specified in {@link #TIME_FORMATS}.
     *
     * @param s the string to attempt to parse into a time
     *
     * @return an object holding the time value
     *
     * @throws DateTimeParseException if the string couldn't be parsed into a
     *         time value.
     */
    public static LocalTime parseTime(String s) {
        for (DateTimeFormatter fmt : TIME_FORMATS) {
            try {
                return fmt.parse(s, LocalTime::from);
            }
            catch (DateTimeParseException e) {
                // That didn't work.  Go on to the next one.
            }
        }

        throw new DateTimeParseException("Could not parse into LocalTime",
            s, 0);
    }


    /**
     * Attempts to parse a string into some kind of temporal value using the
     * formats specified in {@link #DATETIME_FORMATS}, {@link #DATE_FORMATS},
     * and {@link #TIME_FORMATS}.  Attempts are made in this order, so a
     * date/time will be produced if possible, then a date, and finally a
     * time.
     *
     * @param s the string to attempt to parse into a temporal value
     *
     * @return an object holding the temporal value
     *
     * @throws DateTimeParseException if the string couldn't be parsed into a
     *         temporal value.
     */
    public static Temporal parseTemporal(String s) {
        try {
            return parseDateTime(s);
        }
        catch (DateTimeParseException e1) {
            try {
                return parseDate(s);
            }
            catch (DateTimeParseException e2) {
                try {
                    return parseTime(s);
                }
                catch (DateTimeParseException e3) {
                    throw new DateTimeParseException("Could not parse into " +
                        "any kind of Temporal", s, 0);
                }
            }
        }
    }


    /**
     * Attempts to parse a string into some kind of "temporal amount" or
     * INTERVAL value.  Only one interval format is supported at this time:
     * "<tt><em>N</em> [YEAR|MONTH|WEEK|DAY|HOUR|MINUTE|SECOND]S?</tt>", where
     * <em>N</em> is an integer.  The units may be singular or plural; e.g.
     * both "<tt>YEAR</tt>" and "<tt>YEARS</tt>" is supported.
     *
     * @param s the string to attempt to parse into a temporal amount
     *
     * @return an object holding the temporal amount
     *
     * @throws DateTimeParseException if the string couldn't be parsed into a
     *         temporal amount.
     */
    public static TemporalAmount parseInterval(String s) {
        // Split the string on horizontal whitespace.
        s = s.trim();
        String parts[] = s.split("\\h");

        if (parts.length != 2) {
            throw new DateTimeParseException(
                "Could not parse into interval:  " +
                "must contain exactly two parts", s, 0);
        }

        TemporalAmount result;

        int amount;
        try {
            amount = Integer.parseInt(parts[0]);
        }
        catch (NumberFormatException e) {
            throw new DateTimeParseException(
                "Could not parse into interval:  " +
                    "first value must be a nonnegative integer", s, 0);
        }

        String units = parts[1].toLowerCase();

        if ("year".equals(units) || "years".equals(units)) {
            result = Period.ofYears(amount);
        }
        else if ("month".equals(units) || "months".equals(units)) {
            result = Period.ofMonths(amount);
        }
        else if ("week".equals(units) || "weeks".equals(units)) {
            result = Period.ofWeeks(amount);
        }
        else if ("day".equals(units) || "days".equals(units)) {
            result = Period.ofDays(amount);
        }
        else if ("hour".equals(units) || "hours".equals(units)) {
            result = Duration.ofHours(amount);
        }
        else if ("minute".equals(units) || "minutes".equals(units)) {
            result = Duration.ofMinutes(amount);
        }
        else if ("second".equals(units) || "seconds".equals(units)) {
            result = Duration.ofSeconds(amount);
        }
        else {
            throw new DateTimeParseException(
                "Could not parse into interval:  " +
                "second value specifies unrecognized units", s, 0);
        }

        return result;
    }
}