/*
* Licensed 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 com.addthis.hydra.data.util;
import java.util.Iterator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.joda.time.format.DateTimeFormatterBuilder;
/**
* date helper that understands the {{now}} {{now+offset}} {{now-offset}} syntax.
*/
public final class DateUtil {
public static final String NOW_PREFIX = "{{now";
public static final String NOW_POSTFIX = "}}";
public static final String OFF = "{{off}}";
public static final String NOW = NOW_PREFIX + NOW_POSTFIX;
@VisibleForTesting
static final DateTimeFormatter ymdFormatter = new DateTimeFormatterBuilder().appendTwoDigitYear(2000)
.appendMonthOfYear(2)
.appendDayOfMonth(2).toFormatter();
@VisibleForTesting
static final DateTimeFormatter ymdhFormatter = new DateTimeFormatterBuilder().appendTwoDigitYear(2000)
.appendMonthOfYear(2)
.appendDayOfMonth(2)
.appendHourOfDay(2).toFormatter();
private static final Pattern DATE_MACRO_PATTERN = Pattern.compile(
// prefix literal
Pattern.quote("{{now") +
// optional increment or decrement adjustment
"(([+-]\\d+)(h)?)?" +
// optional date time format
"(::.+?)?" +
// optional timezone
"(:.+?)?" +
// suffix literal
Pattern.quote("}}"));
public static DateTime getDateTime(String format, String date) {
return getDateTime(getFormatter(format), date, false);
}
public static DateTime getDateTime(DateTimeFormatter formatter, String date) {
return getDateTime(formatter, date, false);
}
public static DateTime getDateTime(DateTimeFormatter formatter, String date, boolean hourly) {
if (date.startsWith(NOW_PREFIX) && date.endsWith(NOW_POSTFIX)) {
if (date.equals(NOW)) {
return new DateTime();
}
DateTime time = new DateTime();
int pos;
if ((pos = date.indexOf(":")) > 0) {
String timeZone = date.substring(pos + 1, date.length() - NOW_POSTFIX.length());
DateTimeZone zone = DateTimeZone.forID(timeZone);
time = time.withZone(zone);
date = date.substring(0, pos) + NOW_POSTFIX;
}
if ((pos = date.indexOf("+")) > 0) {
int offset = Integer.parseInt(date.substring(pos + 1, date.length() - NOW_POSTFIX.length()));
time = hourly ? time.plusHours(offset) : time.plusDays(offset);
} else if ((pos = date.indexOf("-")) > 0) {
int offset = Integer.parseInt(date.substring(pos + 1, date.length() - NOW_POSTFIX.length()));
time = hourly ? time.minusHours(offset) : time.minusDays(offset);
}
return time;
}
return formatter.parseDateTime(date);
}
public static String format(String format, long time) {
return format(getFormatter(format), time);
}
public static String format(DateTimeFormatter formatter, long time) {
return formatter.print(time);
}
public static DateTimeFormatter getFormatter(String format) {
return DateTimeFormat.forPattern(format);
}
public static Iterator<DateTime> getDayIterator(final DateTime start, final DateTime end) {
return new DateIterator(start, end, false);
}
public static Iterator<DateTime> getHourIterator(final DateTime start, final DateTime end) {
return new DateIterator(start, end, true);
}
/** */
private static class DateIterator implements Iterator<DateTime> {
private final boolean reverse;
private final boolean hourly;
private DateTime start;
private DateTime end;
DateIterator(DateTime start, DateTime end, boolean hourly) {
reverse = start.compareTo(end) > 0;
this.hourly = hourly;
this.start = start;
this.end = end;
}
@Override
public boolean hasNext() {
return reverse ? start.compareTo(end) > 0 : end.compareTo(start) > 0;
}
@Override
public DateTime next() {
DateTime ret = start;
if (reverse) {
if (hourly) {
start.minusHours(1);
} else {
start.plusHours(1);
}
} else {
if (hourly) {
start.minusDays(1);
} else {
start.plusDays(1);
}
}
return ret;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
/**
* Split a path up and replace any {{now-1}}-style elements with the YYMMDD equivalent.
* {{now-1h}} subtracts one hour from the current time and returns a YYMMDDHH formatted string.
*
* @param path The input path to process
* @return The path with the relevant tokens replaced
*/
public static String expandDateMacro(String path) {
StringBuffer sb = new StringBuffer();
Matcher matcher = DATE_MACRO_PATTERN.matcher(path);
while (matcher.find()) {
DateTimeFormatter formatter = null;
boolean hourly = false;
String match;
if (matcher.group(4) != null) {
formatter = DateTimeFormat.forPattern(matcher.group(4).substring(2));
}
if (matcher.group(3) != null) {
match = "{{now" + matcher.group(2) + Strings.nullToEmpty(matcher.group(5)) + "}}";
hourly = true;
formatter = (formatter != null) ? formatter : ymdhFormatter;
} else {
match = "{{now" + matcher.group(1) + Strings.nullToEmpty(matcher.group(5)) + "}}";
formatter = (formatter != null) ? formatter : ymdFormatter;
}
matcher.appendReplacement(sb, com.addthis.hydra.data.util.DateUtil.getDateTime(formatter, match, hourly).toString(formatter));
}
matcher.appendTail(sb);
return sb.toString();
}
}