org.apache.lens.cube.parse.DateUtil.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.lens.cube.parse.DateUtil.java

Source

/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF 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.apache.lens.cube.parse;

import static java.util.Calendar.*;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.lens.cube.metadata.UpdatePeriod;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.time.DateUtils;
import org.apache.hadoop.hive.ql.ErrorMsg;
import org.apache.hadoop.hive.ql.parse.SemanticException;

import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public final class DateUtil {
    private DateUtil() {

    }

    /*
     * NOW -> new java.util.Date() NOW-7DAY -> a date one week earlier NOW (+-)
     * <NUM>UNIT or Hardcoded dates in DD-MM-YYYY hh:mm:ss,sss
     */
    public static final String UNIT = "year|month|week|day|hour|minute|second";
    public static final String GRANULARITY = "\\.(" + UNIT + ")";
    public static final String RELATIVE = "(now){1}(" + GRANULARITY + "){0,1}";
    public static final Pattern P_RELATIVE = Pattern.compile(RELATIVE, Pattern.CASE_INSENSITIVE);

    public static final String WSPACE = "\\s+";
    public static final String OPTIONAL_WSPACE = "\\s*";
    public static final Pattern P_WSPACE = Pattern.compile(WSPACE);

    public static final String SIGNAGE = "\\+|\\-";
    public static final Pattern P_SIGNAGE = Pattern.compile(SIGNAGE);

    public static final String QUANTITY = "\\d+";
    public static final Pattern P_QUANTITY = Pattern.compile(QUANTITY);

    public static final Pattern P_UNIT = Pattern.compile(UNIT, Pattern.CASE_INSENSITIVE);

    public static final String RELDATE_VALIDATOR_STR = RELATIVE + OPTIONAL_WSPACE + "((" + SIGNAGE + ")" + "("
            + WSPACE + ")?" + "(" + QUANTITY + ")" + OPTIONAL_WSPACE + "(" + UNIT + ")){0,1}" + "(s?)";

    public static final Pattern RELDATE_VALIDATOR = Pattern.compile(RELDATE_VALIDATOR_STR,
            Pattern.CASE_INSENSITIVE);

    public static final String YEAR_FMT = "[0-9]{4}";
    public static final String MONTH_FMT = YEAR_FMT + "-[0-9]{2}";
    public static final String DAY_FMT = MONTH_FMT + "-[0-9]{2}";
    public static final String HOUR_FMT = DAY_FMT + "-[0-9]{2}";
    public static final String MINUTE_FMT = HOUR_FMT + ":[0-9]{2}";
    public static final String SECOND_FMT = MINUTE_FMT + ":[0-9]{2}";
    public static final String MILLISECOND_FMT = SECOND_FMT + ",[0-9]{3}";
    public static final String ABSDATE_FMT = "yyyy-MM-dd-HH:mm:ss,SSS";
    public static final String HIVE_QUERY_DATE_FMT = "yyyy-MM-dd HH:mm:ss";

    public static final ThreadLocal<DateFormat> ABSDATE_PARSER = new ThreadLocal<DateFormat>() {
        @Override
        protected SimpleDateFormat initialValue() {
            return new SimpleDateFormat(ABSDATE_FMT);
        }
    };
    public static final ThreadLocal<DateFormat> HIVE_QUERY_DATE_PARSER = new ThreadLocal<DateFormat>() {
        @Override
        protected SimpleDateFormat initialValue() {
            return new SimpleDateFormat(HIVE_QUERY_DATE_FMT);
        }
    };

    public static String formatDate(Date dt) {
        return ABSDATE_PARSER.get().format(dt);
    }

    public static String getAbsDateFormatString(String str) {
        if (str.matches(YEAR_FMT)) {
            return str + "-01-01-00:00:00,000";
        } else if (str.matches(MONTH_FMT)) {
            return str + "-01-00:00:00,000";
        } else if (str.matches(DAY_FMT)) {
            return str + "-00:00:00,000";
        } else if (str.matches(HOUR_FMT)) {
            return str + ":00:00,000";
        } else if (str.matches(MINUTE_FMT)) {
            return str + ":00,000";
        } else if (str.matches(SECOND_FMT)) {
            return str + ",000";
        } else if (str.matches(MILLISECOND_FMT)) {
            return str;
        }
        throw new IllegalArgumentException("Unsupported formatting for date" + str);
    }

    public static Date resolveDate(String str, Date now) throws SemanticException {
        if (RELDATE_VALIDATOR.matcher(str).matches()) {
            return resolveRelativeDate(str, now);
        } else {
            return resolveAbsoluteDate(str);
        }
    }

    public static String relativeToAbsolute(String relative) throws SemanticException {
        return relativeToAbsolute(relative, new Date());
    }

    public static String relativeToAbsolute(String relative, Date now) throws SemanticException {
        if (RELDATE_VALIDATOR.matcher(relative).matches()) {
            return ABSDATE_PARSER.get().format(resolveRelativeDate(relative, now));
        } else {
            return relative;
        }
    }

    public static Date resolveAbsoluteDate(String str) throws SemanticException {
        try {
            return ABSDATE_PARSER.get().parse(getAbsDateFormatString(str));
        } catch (ParseException e) {
            log.error("Invalid date format. expected only {} date provided:{}", ABSDATE_FMT, str, e);
            throw new SemanticException(e, ErrorMsg.WRONG_TIME_RANGE_FORMAT, ABSDATE_FMT, str);
        }
    }

    public static Date resolveRelativeDate(String str, Date now) throws SemanticException {
        if (StringUtils.isBlank(str)) {
            throw new SemanticException(ErrorMsg.NULL_DATE_VALUE);
        }

        // Resolve NOW with proper granularity
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(now);

        str = str.toLowerCase();
        Matcher relativeMatcher = P_RELATIVE.matcher(str);
        if (relativeMatcher.find()) {
            String nowWithGranularity = relativeMatcher.group();
            nowWithGranularity = nowWithGranularity.replaceAll("now", "");
            nowWithGranularity = nowWithGranularity.replaceAll("\\.", "");

            Matcher granularityMatcher = P_UNIT.matcher(nowWithGranularity);
            if (granularityMatcher.find()) {
                String unit = granularityMatcher.group().toLowerCase();
                if ("year".equals(unit)) {
                    calendar = DateUtils.truncate(calendar, YEAR);
                } else if ("month".equals(unit)) {
                    calendar = DateUtils.truncate(calendar, MONTH);
                } else if ("week".equals(unit)) {
                    calendar.set(Calendar.DAY_OF_WEEK, Calendar.SUNDAY);
                    calendar = DateUtils.truncate(calendar, DAY_OF_MONTH);
                } else if ("day".equals(unit)) {
                    calendar = DateUtils.truncate(calendar, DAY_OF_MONTH);
                } else if ("hour".equals(unit)) {
                    calendar = DateUtils.truncate(calendar, Calendar.HOUR_OF_DAY);
                } else if ("minute".equals(unit)) {
                    calendar = DateUtils.truncate(calendar, Calendar.MINUTE);
                } else if ("second".equals(unit)) {
                    calendar = DateUtils.truncate(calendar, Calendar.SECOND);
                } else {
                    throw new SemanticException(ErrorMsg.INVALID_TIME_UNIT, unit);
                }
            }
        }

        // Get rid of 'now' part and whitespace
        String diffStr = str.replaceAll(RELATIVE, "").replace(WSPACE, "");
        TimeDiff diff = TimeDiff.parseFrom(diffStr);
        return diff.offsetFrom(calendar.getTime());
    }

    public static Date getCeilDate(Date fromDate, UpdatePeriod interval) {
        Calendar cal = Calendar.getInstance();
        cal.setTime(fromDate);
        boolean hasFraction = false;
        switch (interval) {
        case YEARLY:
            if (cal.get(MONTH) != 0) {
                hasFraction = true;
                break;
            }
        case MONTHLY:
            if (cal.get(DAY_OF_MONTH) != 1) {
                hasFraction = true;
                break;
            }
        case DAILY:
            if (cal.get(Calendar.HOUR_OF_DAY) != 0) {
                hasFraction = true;
                break;
            }
        case HOURLY:
            if (cal.get(Calendar.MINUTE) != 0) {
                hasFraction = true;
                break;
            }
        case MINUTELY:
            if (cal.get(Calendar.SECOND) != 0) {
                hasFraction = true;
                break;
            }
        case SECONDLY:
        case CONTINUOUS:
            if (cal.get(Calendar.MILLISECOND) != 0) {
                hasFraction = true;
            }
            break;
        case WEEKLY:
            if (cal.get(Calendar.DAY_OF_WEEK) != 1) {
                hasFraction = true;
                break;
            }
        }

        if (hasFraction) {
            cal.add(interval.calendarField(), 1);
            return getFloorDate(cal.getTime(), interval);
        } else {
            return fromDate;
        }
    }

    public static Date getFloorDate(Date toDate, UpdatePeriod interval) {
        Calendar cal = Calendar.getInstance();
        cal.setTime(toDate);
        switch (interval) {
        case YEARLY:
            cal.set(MONTH, 0);
        case MONTHLY:
            cal.set(DAY_OF_MONTH, 1);
        case DAILY:
            cal.set(Calendar.HOUR_OF_DAY, 0);
        case HOURLY:
            cal.set(Calendar.MINUTE, 0);
        case MINUTELY:
            cal.set(Calendar.SECOND, 0);
        case SECONDLY:
        case CONTINUOUS:
            cal.set(Calendar.MILLISECOND, 0);
            break;
        case WEEKLY:
            cal.set(Calendar.DAY_OF_WEEK, 1);
            cal.set(Calendar.HOUR_OF_DAY, 0);
            cal.set(Calendar.MINUTE, 0);
            cal.set(Calendar.SECOND, 0);
            cal.set(Calendar.MILLISECOND, 0);
            break;
        }
        return cal.getTime();
    }

    public static int getNumberofDaysInMonth(Date date) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        return calendar.getActualMaximum(DAY_OF_MONTH);
    }

    public static CoveringInfo getMonthlyCoveringInfo(Date from, Date to) {
        // Move 'from' to end of month, unless its the first day of month
        boolean coverable = true;
        if (!from.equals(DateUtils.truncate(from, MONTH))) {
            from = DateUtils.addMonths(DateUtils.truncate(from, MONTH), 1);
            coverable = false;
        }

        // Move 'to' to beginning of next month, unless its the first day of the month
        if (!to.equals(DateUtils.truncate(to, MONTH))) {
            to = DateUtils.truncate(to, MONTH);
            coverable = false;
        }

        int months = 0;
        while (from.before(to)) {
            from = DateUtils.addMonths(from, 1);
            months++;
        }
        return new CoveringInfo(months, coverable);
    }

    public static CoveringInfo getQuarterlyCoveringInfo(Date from, Date to) {
        CoveringInfo monthlyCoveringInfo = getMonthlyCoveringInfo(from, to);
        if (monthlyCoveringInfo.getCountBetween() < 3) {
            return new CoveringInfo(0, false);
        }
        boolean coverable = monthlyCoveringInfo.isCoverable();
        if (!from.equals(DateUtils.truncate(from, MONTH))) {
            from = DateUtils.addMonths(DateUtils.truncate(from, MONTH), 1);
            coverable = false;
        }
        Calendar cal = Calendar.getInstance();
        cal.setTime(from);
        int fromMonth = cal.get(MONTH);

        // Get the start date of the quarter
        int beginOffset = (3 - fromMonth % 3) % 3;
        int endOffset = (monthlyCoveringInfo.getCountBetween() - beginOffset) % 3;
        if (beginOffset > 0 || endOffset > 0) {
            coverable = false;
        }
        return new CoveringInfo((monthlyCoveringInfo.getCountBetween() - beginOffset - endOffset) / 3, coverable);
    }

    public static CoveringInfo getYearlyCoveringInfo(Date from, Date to) {
        CoveringInfo monthlyCoveringInfo = getMonthlyCoveringInfo(from, to);
        if (monthlyCoveringInfo.getCountBetween() < 12) {
            return new CoveringInfo(0, false);
        }
        boolean coverable = monthlyCoveringInfo.isCoverable();
        if (!from.equals(DateUtils.truncate(from, MONTH))) {
            from = DateUtils.addMonths(DateUtils.truncate(from, MONTH), 1);
            coverable = false;
        }
        Calendar cal = Calendar.getInstance();
        cal.setTime(from);
        int fromMonth = cal.get(MONTH);
        int beginOffset = (12 - fromMonth % 12) % 12;
        int endOffset = (monthlyCoveringInfo.getCountBetween() - beginOffset) % 12;
        if (beginOffset > 0 || endOffset > 0) {
            coverable = false;
        }
        return new CoveringInfo((monthlyCoveringInfo.getCountBetween() - beginOffset - endOffset) / 12, coverable);
    }

    public static CoveringInfo getWeeklyCoveringInfo(Date from, Date to) {
        int dayDiff = 0;
        Date tmpFrom = from;
        while (tmpFrom.before(to)) {
            tmpFrom = DateUtils.addDays(tmpFrom, 1);
            dayDiff++;
        }

        if (dayDiff < 7) {
            return new CoveringInfo(0, false);
        }

        Calendar cal = Calendar.getInstance();
        cal.setTime(from);
        int fromWeek = cal.get(Calendar.WEEK_OF_YEAR);
        int fromDay = cal.get(Calendar.DAY_OF_WEEK);
        int fromYear = cal.get(YEAR);

        cal.clear();
        cal.set(YEAR, fromYear);
        cal.set(Calendar.WEEK_OF_YEAR, fromWeek);
        cal.set(Calendar.DAY_OF_WEEK, Calendar.SUNDAY);
        int maxDayInWeek = cal.getActualMaximum(Calendar.DAY_OF_WEEK);
        Date fromWeekStartDate = cal.getTime();
        boolean coverable = dayDiff % 7 == 0;
        if (fromWeekStartDate.before(from)) {
            // Count from the start of next week
            dayDiff -= (maxDayInWeek - (fromDay - Calendar.SUNDAY));
            coverable = false;
        }

        return new CoveringInfo(dayDiff / 7, coverable);
    }

    static CoveringInfo getCoveringInfo(Date from, Date to, UpdatePeriod interval) {
        switch (interval) {
        case SECONDLY:
        case CONTINUOUS:
            return getMilliSecondCoveringInfo(from, to, 1000);
        case MINUTELY:
            return getMilliSecondCoveringInfo(from, to, 1000 * 60);
        case HOURLY:
            return getMilliSecondCoveringInfo(from, to, 1000 * 60 * 60);
        case DAILY:
            return getMilliSecondCoveringInfo(from, to, 1000 * 60 * 60 * 24);
        case WEEKLY:
            return getWeeklyCoveringInfo(from, to);
        case MONTHLY:
            return getMonthlyCoveringInfo(from, to);
        case QUARTERLY:
            return getQuarterlyCoveringInfo(from, to);
        case YEARLY:
            return getYearlyCoveringInfo(from, to);
        default:
            return new CoveringInfo(0, false);
        }
    }

    private static CoveringInfo getMilliSecondCoveringInfo(Date from, Date to, int millisInInterval) {
        long diff = to.getTime() - from.getTime();
        return new CoveringInfo((int) (diff / millisInInterval), diff % millisInInterval == 0);
    }

    static boolean isCoverableBy(Date from, Date to, Set<UpdatePeriod> intervals) {
        for (UpdatePeriod period : intervals) {
            if (getCoveringInfo(from, to, period).isCoverable()) {
                return true;
            }
        }
        return false;
    }

    public static int getTimeDiff(Date fromDate, Date toDate, UpdatePeriod updatePeriod) {
        return getCoveringInfo(fromDate, toDate, updatePeriod).getCountBetween();
    }

    @Data
    public static class CoveringInfo {
        int countBetween;
        boolean coverable;

        public CoveringInfo(int countBetween, boolean coverable) {
            this.countBetween = countBetween;
            this.coverable = coverable;
        }
    }

    @EqualsAndHashCode
    static class TimeDiff {
        int quantity;
        int calendarField;

        public TimeDiff(int quantity, int calendarField) {
            this.quantity = quantity;
            this.calendarField = calendarField;
        }

        static TimeDiff parseFrom(String diffStr) throws SemanticException {
            // Get the relative diff part to get eventual date based on now.
            Matcher qtyMatcher = P_QUANTITY.matcher(diffStr);
            int qty = 1;
            if (qtyMatcher.find()) {
                qty = Integer.parseInt(qtyMatcher.group());
            }

            Matcher signageMatcher = P_SIGNAGE.matcher(diffStr);
            if (signageMatcher.find()) {
                String sign = signageMatcher.group();
                if ("-".equals(sign)) {
                    qty = -qty;
                }
            }

            Matcher unitMatcher = P_UNIT.matcher(diffStr);
            if (unitMatcher.find()) {
                String unit = unitMatcher.group().toLowerCase();
                if ("year".equals(unit)) {
                    return new TimeDiff(qty, YEAR);
                } else if ("month".equals(unit)) {
                    return new TimeDiff(qty, MONTH);
                } else if ("week".equals(unit)) {
                    return new TimeDiff(7 * qty, DAY_OF_MONTH);
                } else if ("day".equals(unit)) {
                    return new TimeDiff(qty, DAY_OF_MONTH);
                } else if ("hour".equals(unit)) {
                    return new TimeDiff(qty, HOUR_OF_DAY);
                } else if ("minute".equals(unit)) {
                    return new TimeDiff(qty, MINUTE);
                } else if ("second".equals(unit)) {
                    return new TimeDiff(qty, SECOND);
                } else {
                    throw new SemanticException(ErrorMsg.INVALID_TIME_UNIT, unit);
                }
            }
            return new TimeDiff(0, SECOND);
        }

        public Date offsetFrom(Date time) {
            return DateUtils.add(time, calendarField, quantity);
        }

        public Date negativeOffsetFrom(Date time) {
            return DateUtils.add(time, calendarField, -quantity);
        }
    }

}