/**
* Copyright 2011-2017 Asakusa Framework Team.
*
* 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.asakusafw.runtime.io.text.value;
import static com.asakusafw.runtime.io.text.value.DateAdapter.*;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.TimeZone;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.asakusafw.runtime.value.DateUtil;
abstract class DateTimeAdapter {
private static final Factory[] BUILTIN = new Factory[] {
(pattern, zone) -> {
if (isLocal(zone) && pattern.equals(Direct.PATTERN)) {
return new Direct();
}
return null;
},
(pattern, zone) -> {
return isLocal(zone) ? Standard.of(pattern) : null;
},
Default::new,
};
abstract String getPattern();
abstract long parse(CharSequence sequence);
abstract void emit(long elapsedSeconds, StringBuilder output);
static DateTimeAdapter newInstance(String pattern, String timeZone) {
for (Factory f : BUILTIN) {
DateTimeAdapter formatter = f.of(pattern, timeZone);
if (formatter != null) {
return formatter;
}
}
throw new AssertionError(pattern);
}
private static boolean isLocal(String timeZone) {
return timeZone == null;
}
@FunctionalInterface
private interface Factory {
DateTimeAdapter of(String pattern, String timeZone);
}
private static final class Default extends DateTimeAdapter {
private final SimpleDateFormat format;
private final Calendar calendarBuffer = Calendar.getInstance();
private final ParsePosition parsePositionBuffer = new ParsePosition(0);
Default(String pattern, String timeZone) {
this.format = new SimpleDateFormat(pattern);
if (timeZone != null) {
this.format.setTimeZone(TimeZone.getTimeZone(timeZone));
}
}
@Override
String getPattern() {
return format.toPattern();
}
@Override
long parse(CharSequence sequence) {
ParsePosition pos = parsePositionBuffer;
pos.setIndex(0);
pos.setErrorIndex(-1);
java.util.Date parsed = format.parse(sequence.toString(), pos);
if (pos.getIndex() != sequence.length() || pos.getErrorIndex() >= 0) {
return -1;
}
calendarBuffer.setTime(parsed);
return DateUtil.getSecondFromCalendar(calendarBuffer);
}
@Override
void emit(long elapsedSeconds, StringBuilder output) {
DateUtil.setSecondToCalendar(elapsedSeconds, calendarBuffer);
output.append(format.format(calendarBuffer.getTime()));
}
}
private static final class Standard extends DateTimeAdapter {
private static final Pattern META_PATTERN = Pattern.compile(
"yyyy([ \\-\\._/]|'[a-zA-Z \\-\\._/]')MM\\1dd" //$NON-NLS-1$
+ "([ \\-\\._]|'[a-zA-Z \\-\\._]')" //$NON-NLS-1$
+ "HH([ \\-\\.:_]|'[a-zA-Z \\-\\.:_]')mm\\3ss"); //$NON-NLS-1$
private final DateTimeAdapter next;
private final char dateSegmentSeparator;
private final char dateTimeSeparator;
private final char timeSegmentSeparator;
private Standard(
String pattern,
char dateSegmentSeparator, char dateTimeSeparator, char timeSegmentSeparator) {
this.dateSegmentSeparator = dateSegmentSeparator;
this.dateTimeSeparator = dateTimeSeparator;
this.timeSegmentSeparator = timeSegmentSeparator;
this.next = new Default(pattern, null);
}
static Standard of(String pattern) {
Matcher matcher = META_PATTERN.matcher(pattern);
if (matcher.matches()) {
char dateSegment = extract(matcher, 1);
char dateTime = extract(matcher, 2);
char timeSegment = extract(matcher, 3);
return new Standard(pattern, dateSegment, dateTime, timeSegment);
}
return null;
}
private static char extract(Matcher matcher, int group) {
String value = matcher.group(group);
if (value.length() == 1) {
return value.charAt(0);
}
if (value.length() == 3) {
if (value.charAt(0) != '\'' && value.charAt(2) != '\'') {
throw new IllegalStateException();
}
return value.charAt(1);
}
throw new IllegalStateException();
}
@Override
String getPattern() {
return next.getPattern();
}
@Override
long parse(CharSequence sequence) {
long value = DateUtil.parseDateTime(
sequence, dateSegmentSeparator, dateTimeSeparator, timeSegmentSeparator);
if (value >= 0) {
return value;
}
return next.parse(sequence);
}
@Override
void emit(long elapsedSeconds, StringBuilder output) {
DateUtil.toDateTimeString(
elapsedSeconds,
dateSegmentSeparator, dateTimeSeparator, timeSegmentSeparator,
output);
}
}
private static final class Direct extends DateTimeAdapter {
static final String PATTERN = "yyyyMMddHHmmss"; //$NON-NLS-1$
private static final int POS_YEAR = 0;
private static final int POS_MONTH = 4;
private static final int POS_DAY = 6;
private static final int POS_HOUR = 8;
private static final int POS_MINUTE = 10;
private static final int POS_SECOND = 12;
private static final int LENGTH = 14;
private final DateTimeAdapter next;
Direct() {
this.next = new Default(PATTERN, null);
}
@Override
String getPattern() {
return next.getPattern();
}
@Override
long parse(CharSequence sequence) {
if (sequence.length() != LENGTH) {
return next.parse(sequence);
}
int year = getNumericValue(sequence, POS_YEAR, 4);
int month = getNumericValue(sequence, POS_MONTH, 2);
int day = getNumericValue(sequence, POS_DAY, 2);
int hour = getNumericValue(sequence, POS_HOUR, 2);
int minute = getNumericValue(sequence, POS_MINUTE, 2);
int second = getNumericValue(sequence, POS_SECOND, 2);
if (year < 0 || month < 0 || day < 0 || hour < 0 || minute < 0 || second < 0) {
return next.parse(sequence);
}
int date = DateUtil.getDayFromDate(year, month, day);
int secondsInDay = DateUtil.getSecondFromTime(hour, minute, second);
return (long) date * 86400 + secondsInDay;
}
@Override
void emit(long elapsedSeconds, StringBuilder output) {
int elapsedDate = DateUtil.getDayFromSeconds(elapsedSeconds);
int year = DateUtil.getYearFromDay(elapsedDate);
int dayInYear = elapsedDate - DateUtil.getDayFromYear(year);
int month = DateUtil.getMonthOfYear(dayInYear, DateUtil.isLeap(year));
int day = DateUtil.getDayOfMonth(dayInYear, DateUtil.isLeap(year));
int secondOfDay = DateUtil.getSecondOfDay(elapsedSeconds);
int hour = secondOfDay / (60 * 60);
int minute = secondOfDay / 60 % 60;
int second = secondOfDay % 60;
append(output, year, 4);
append(output, month, 2);
append(output, day, 2);
append(output, hour, 2);
append(output, minute, 2);
append(output, second, 2);
}
}
}