org.elasticsearch.common.joda.DateMathParser.java Source code

Java tutorial

Introduction

Here is the source code for org.elasticsearch.common.joda.DateMathParser.java

Source

/*
 * Licensed to Elasticsearch under one or more contributor
 * license agreements. See the NOTICE file distributed with
 * this work for additional information regarding copyright
 * ownership. Elasticsearch licenses this file to you under
 * the Apache License, Version 2.0 (the "License"); you may
 * not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package org.elasticsearch.common.joda;

import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.ElasticsearchParseException;
import org.joda.time.DateTimeZone;
import org.joda.time.MutableDateTime;
import org.joda.time.format.DateTimeFormatter;

import java.util.concurrent.TimeUnit;

/**
 * A parser for date/time formatted text with optional date math.
 * 
 * The format of the datetime is configurable, and unix timestamps can also be used. Datemath
 * is appended to a datetime with the following syntax:
 * <code>||[+-/](\d+)?[yMwdhHms]</code>.
 */
public class DateMathParser {

    private final FormatDateTimeFormatter dateTimeFormatter;
    private final TimeUnit timeUnit;

    public DateMathParser(FormatDateTimeFormatter dateTimeFormatter, TimeUnit timeUnit) {
        if (dateTimeFormatter == null)
            throw new NullPointerException();
        if (timeUnit == null)
            throw new NullPointerException();
        this.dateTimeFormatter = dateTimeFormatter;
        this.timeUnit = timeUnit;
    }

    public long parse(String text, long now) {
        return parse(text, now, false, null);
    }

    public long parse(String text, long now, boolean roundUp, DateTimeZone timeZone) {
        long time;
        String mathString;
        if (text.startsWith("now")) {
            time = now;
            mathString = text.substring("now".length());
        } else {
            int index = text.indexOf("||");
            if (index == -1) {
                return parseDateTime(text, timeZone);
            }
            time = parseDateTime(text.substring(0, index), timeZone);
            mathString = text.substring(index + 2);
            if (mathString.isEmpty()) {
                return time;
            }
        }

        return parseMath(mathString, time, roundUp);
    }

    private long parseMath(String mathString, long time, boolean roundUp) throws ElasticsearchParseException {
        MutableDateTime dateTime = new MutableDateTime(time, DateTimeZone.UTC);
        for (int i = 0; i < mathString.length();) {
            char c = mathString.charAt(i++);
            final boolean round;
            final int sign;
            if (c == '/') {
                round = true;
                sign = 1;
            } else {
                round = false;
                if (c == '+') {
                    sign = 1;
                } else if (c == '-') {
                    sign = -1;
                } else {
                    throw new ElasticsearchParseException(
                            "operator not supported for date math [" + mathString + "]");
                }
            }

            if (i >= mathString.length()) {
                throw new ElasticsearchParseException("truncated date math [" + mathString + "]");
            }

            final int num;
            if (!Character.isDigit(mathString.charAt(i))) {
                num = 1;
            } else {
                int numFrom = i;
                while (i < mathString.length() && Character.isDigit(mathString.charAt(i))) {
                    i++;
                }
                if (i >= mathString.length()) {
                    throw new ElasticsearchParseException("truncated date math [" + mathString + "]");
                }
                num = Integer.parseInt(mathString.substring(numFrom, i));
            }
            if (round) {
                if (num != 1) {
                    throw new ElasticsearchParseException(
                            "rounding `/` can only be used on single unit types [" + mathString + "]");
                }
            }
            char unit = mathString.charAt(i++);
            MutableDateTime.Property propertyToRound = null;
            switch (unit) {
            case 'y':
                if (round) {
                    propertyToRound = dateTime.yearOfCentury();
                } else {
                    dateTime.addYears(sign * num);
                }
                break;
            case 'M':
                if (round) {
                    propertyToRound = dateTime.monthOfYear();
                } else {
                    dateTime.addMonths(sign * num);
                }
                break;
            case 'w':
                if (round) {
                    propertyToRound = dateTime.weekOfWeekyear();
                } else {
                    dateTime.addWeeks(sign * num);
                }
                break;
            case 'd':
                if (round) {
                    propertyToRound = dateTime.dayOfMonth();
                } else {
                    dateTime.addDays(sign * num);
                }
                break;
            case 'h':
            case 'H':
                if (round) {
                    propertyToRound = dateTime.hourOfDay();
                } else {
                    dateTime.addHours(sign * num);
                }
                break;
            case 'm':
                if (round) {
                    propertyToRound = dateTime.minuteOfHour();
                } else {
                    dateTime.addMinutes(sign * num);
                }
                break;
            case 's':
                if (round) {
                    propertyToRound = dateTime.secondOfMinute();
                } else {
                    dateTime.addSeconds(sign * num);
                }
                break;
            default:
                throw new ElasticsearchParseException(
                        "unit [" + unit + "] not supported for date math [" + mathString + "]");
            }
            if (propertyToRound != null) {
                if (roundUp) {
                    // we want to go up to the next whole value, even if we are already on a rounded value
                    propertyToRound.add(1);
                    propertyToRound.roundFloor();
                    dateTime.addMillis(-1); // subtract 1 millisecond to get the largest inclusive value
                } else {
                    propertyToRound.roundFloor();
                }
            }
        }
        return dateTime.getMillis();
    }

    private long parseDateTime(String value, DateTimeZone timeZone) {

        // first check for timestamp
        if (value.length() > 4 && StringUtils.isNumeric(value)) {
            try {
                long time = Long.parseLong(value);
                return timeUnit.toMillis(time);
            } catch (NumberFormatException e) {
                throw new ElasticsearchParseException("failed to parse date field [" + value + "] as timestamp", e);
            }
        }

        DateTimeFormatter parser = dateTimeFormatter.parser();
        if (timeZone != null) {
            parser = parser.withZone(timeZone);
        }
        try {
            return parser.parseMillis(value);
        } catch (IllegalArgumentException e) {

            throw new ElasticsearchParseException(
                    "failed to parse date field [" + value + "] with format [" + dateTimeFormatter.format() + "]",
                    e);
        }
    }

}