Java tutorial
/** * 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.metadata; import static java.util.Calendar.MONTH; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Stream; import org.apache.lens.cube.error.LensCubeErrorCode; import org.apache.lens.server.api.error.LensException; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.time.DateUtils; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; 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; public static final Date MAX_DATE = new Date(Long.MAX_VALUE); public static final Date MIN_DATE = new Date(Long.MIN_VALUE); static { StringBuilder sb = new StringBuilder(); String sep = ""; for (UpdatePeriod up : UpdatePeriod.values()) { sb.append(sep).append(up.getUnitName()); sep = "|"; } UNIT = sb.toString(); } public static final String GRANULARITY = "\\.(" + UNIT + ")"; public static final String RELATIVE = "(now)(" + GRANULARITY + ")?"; 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 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 + "))?" + "(s?)"; public static final Pattern RELDATE_VALIDATOR = Pattern.compile(RELDATE_VALIDATOR_STR, Pattern.CASE_INSENSITIVE); public static final Pattern TIMESTAMP_VALIDATOR = Pattern.compile("\\d{5,}"); 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 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 LensException { if (TIMESTAMP_VALIDATOR.matcher(str).matches()) { return new Date(Long.parseLong(str)); } else if (RELDATE_VALIDATOR.matcher(str).matches()) { return resolveRelativeDate(str, now); } else { return resolveAbsoluteDate(str); } } public static String relativeToAbsolute(String relative) throws LensException { return relativeToAbsolute(relative, new Date()); } public static String relativeToAbsolute(String relative, Date now) throws LensException { if (RELDATE_VALIDATOR.matcher(relative).matches()) { return formatAbsDate(resolveRelativeDate(relative, now)); } else { return relative; } } public static String formatAbsDate(Date date) { return ABSDATE_PARSER.get().format(date).replaceAll("([-:,]0+)+$", ""); } static Cache<String, Date> stringToDateCache = CacheBuilder.newBuilder().expireAfterWrite(2, TimeUnit.HOURS) .maximumSize(100).build(); public static Date resolveAbsoluteDate(final String str) throws LensException { try { return stringToDateCache.get(str, () -> ABSDATE_PARSER.get().parse(getAbsDateFormatString(str))); } catch (Exception e) { log.error("Invalid date format. expected only {} date provided:{}", ABSDATE_FMT, str, e); throw new LensException(LensCubeErrorCode.WRONG_TIME_RANGE_FORMAT.getLensErrorInfo(), ABSDATE_FMT, str); } } public static Date resolveRelativeDate(String str, Date now) throws LensException { if (StringUtils.isBlank(str)) { throw new LensException(LensCubeErrorCode.NULL_DATE_VALUE.getLensErrorInfo()); } // 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()) { calendar = UpdatePeriod.fromUnitName(granularityMatcher.group().toLowerCase()).truncate(calendar); } } // 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 date, UpdatePeriod interval) { return interval.getCeilDate(date); } public static Date getFloorDate(Date date, UpdatePeriod interval) { return interval.getFloorDate(date); } 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 fromDay = cal.get(Calendar.DAY_OF_WEEK); cal.set(Calendar.DAY_OF_WEEK, Calendar.SUNDAY); Date fromWeekStartDate = cal.getTime(); boolean coverable = dayDiff % 7 == 0; if (fromWeekStartDate.before(from)) { // Count from the start of next week dayDiff -= (cal.getActualMaximum(Calendar.DAY_OF_WEEK) - (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, interval); case MINUTELY: case HOURLY: case DAILY: return getMilliSecondCoveringInfo(from, to, interval.weight(), interval); 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, long millisInInterval, UpdatePeriod interval) { long diff = to.getTime() - from.getTime(); return new CoveringInfo((int) (diff / millisInInterval), Stream.of(from, to).allMatch(a -> interval.truncate(a).equals(a))); // start date and end date should lie on boundaries. } /** * Whether the range [from,to) is coverable by intervals * @param from from time * @param to to time * @param intervals intervals to check * @return true if any of the intervals can completely cover the range */ static boolean isCoverableBy(Date from, Date to, Set<UpdatePeriod> intervals) { return intervals.stream().anyMatch(period -> isCoverableBy(from, to, period)); } private static boolean isCoverableBy(Date from, Date to, UpdatePeriod period) { return getCoveringInfo(from, to, period).isCoverable(); } public static int getTimeDiff(Date fromDate, Date toDate, UpdatePeriod updatePeriod) { if (fromDate.before(toDate)) { return getCoveringInfo(fromDate, toDate, updatePeriod).getCountBetween(); } else { return -getCoveringInfo(toDate, fromDate, updatePeriod).getCountBetween(); } } @Data public static class CoveringInfo { int countBetween; boolean coverable; public CoveringInfo(int countBetween, boolean coverable) { this.countBetween = countBetween; this.coverable = coverable; } } @EqualsAndHashCode public static class TimeDiff { int quantity; UpdatePeriod updatePeriod; private TimeDiff(int quantity, UpdatePeriod updatePeriod) { this.quantity = quantity; this.updatePeriod = updatePeriod; } public static TimeDiff parseFrom(String diffStr) throws LensException { // 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()) { return new TimeDiff(qty, UpdatePeriod.fromUnitName(unitMatcher.group().toLowerCase())); } return new TimeDiff(0, UpdatePeriod.CONTINUOUS); } public Date offsetFrom(Date time) { return DateUtils.add(time, updatePeriod.calendarField(), quantity); } public Date negativeOffsetFrom(Date time) { return DateUtils.add(time, updatePeriod.calendarField(), -quantity); } } }