public class LowProbabilityFormatFilter extends Filter {
 
    @Override
    public boolean isValid(String text, ChronoOption options, ParsedResult result) {
        return !Pattern.matches("^\\d\\.\\d{1,2}$", result.text);
    }
    
    
} public class PrefixCheckFilter extends Filter {
 
    @Override
    public boolean isValid(String text, ChronoOption options, ParsedResult result) {
        String prefix = text.substring(0, result.index); 
        return !Pattern.matches("(\\d+\\/)$", prefix);
    }
    
    
} public interface Parser {
    List<ParsedResult> execute(String text, Date refDate, ChronoOption option);
} public class ExtractConcordanceRefiner implements Refiner {

    public static final int CONCORDANCE_SIZE = 30;

    public List<ParsedResult> refine(List<ParsedResult> results, String text, ChronoOption options) {

	for (ParsedResult result : results) {

	    String preText = text.substring(0, result.index);
	    preText = preText.replaceAll("\\r\\n|\\n|\\r", " ");
	    preText = preText.replaceAll("\\s+", " ");
	    if (preText.length() > CONCORDANCE_SIZE)
		preText = preText.substring(preText.length() - CONCORDANCE_SIZE,
				preText.length());

	    String posText = text.substring(result.index + result.text.length());
	    posText = preText.replaceAll("\\r\\n|\\n|\\r", " ");
	    posText = preText.replaceAll("\\s+", " ");

	    if (posText.length() > CONCORDANCE_SIZE)
		posText = posText.substring(0, CONCORDANCE_SIZE);

	    result.concordance = preText + result.text + posText;
	}

	return results;
    }
} public class ExtractTimezoneRefiner implements Refiner {

    public static final Pattern TIMEZONE_OFFSET_PATTERN = Pattern
            .compile("^\\s*(GMT|UTC)?(\\+|\\-)(\\d{1,2}):?(\\d{2})");

    public static final Pattern TIMEZONE_NAME_PATTERN = Pattern
            .compile("^\\s*\\(?([A-Z]{1,4})\\)?(?=\\W|$)");

    public static final Map<String, Integer> TIMEZONE_ABBR_MAP = new HashMap<String, Integer>();

    public List<ParsedResult> refine(List<ParsedResult> results, String text, ChronoOption options) {


        for (ParsedResult result : results) {

            if (result.start.isCertain(Components.TimezoneOffset))
                continue;

            Matcher timezoneOffsetMatcher = TIMEZONE_OFFSET_PATTERN.matcher(text.substring(result.index + result.text.length()));
            if (timezoneOffsetMatcher.find()) {

                int hourOffset = Integer.parseInt(timezoneOffsetMatcher.group(3));
                int minuteOffset = Integer.parseInt(timezoneOffsetMatcher.group(4));
                int timezoneOffset = hourOffset * 60 + minuteOffset;
                if (timezoneOffsetMatcher.group(2).equals("-"))
                    timezoneOffset = -timezoneOffset;

                if (result.end != null) {
                    result.end.assign(Components.TimezoneOffset, timezoneOffset);
                }

                result.start.assign(Components.TimezoneOffset, timezoneOffset);
                result.text += timezoneOffsetMatcher.group();
                result.tags.add(ExtractTimezoneRefiner.class.getName());
            }

            Matcher timezoneNameMatcher = TIMEZONE_NAME_PATTERN.matcher(text.substring(result.index + result.text.length()));
            if (timezoneNameMatcher.find()) {

                String timezoneAbbr = timezoneNameMatcher.group(1);
                if (TIMEZONE_ABBR_MAP.containsKey(timezoneAbbr)) {

                    int timezoneOffset = TIMEZONE_ABBR_MAP.get(timezoneAbbr);

                    if (!result.start.isCertain(Components.TimezoneOffset)) {
                        result.start.assign(Components.TimezoneOffset, timezoneOffset);
                    }

                    if (result.end != null && !result.start.isCertain(Components.TimezoneOffset)) {
                        result.end.assign(Components.TimezoneOffset, timezoneOffset);
                    }

                    result.text += timezoneNameMatcher.group();
                    result.tags.add(ExtractTimezoneRefiner.class.getName());
                }
            }
        }

        return results;
    }

    static {
        TIMEZONE_ABBR_MAP.put("A", 60);
        TIMEZONE_ABBR_MAP.put("ACDT", 630);
        TIMEZONE_ABBR_MAP.put("ACST", 570);
        TIMEZONE_ABBR_MAP.put("ADT", -180);
        TIMEZONE_ABBR_MAP.put("AEDT", 660);
        TIMEZONE_ABBR_MAP.put("AEST", 600);
        TIMEZONE_ABBR_MAP.put("AFT", 270);
        TIMEZONE_ABBR_MAP.put("AKDT", -480);
        TIMEZONE_ABBR_MAP.put("AKST", -540);
        TIMEZONE_ABBR_MAP.put("ALMT", 360);
        TIMEZONE_ABBR_MAP.put("AMST", -180);
        TIMEZONE_ABBR_MAP.put("AMT", -240);
        TIMEZONE_ABBR_MAP.put("ANAST", 720);
        TIMEZONE_ABBR_MAP.put("ANAT", 720);
        TIMEZONE_ABBR_MAP.put("AQTT", 300);
        TIMEZONE_ABBR_MAP.put("ART", -180);
        TIMEZONE_ABBR_MAP.put("AST", -240);
        TIMEZONE_ABBR_MAP.put("AWDT", 540);
        TIMEZONE_ABBR_MAP.put("AWST", 480);
        TIMEZONE_ABBR_MAP.put("AZOST", 0);
        TIMEZONE_ABBR_MAP.put("AZOT", -60);
        TIMEZONE_ABBR_MAP.put("AZST", 300);
        TIMEZONE_ABBR_MAP.put("AZT", 240);
        TIMEZONE_ABBR_MAP.put("B", 120);
        TIMEZONE_ABBR_MAP.put("BNT", 480);
        TIMEZONE_ABBR_MAP.put("BOT", -240);
        TIMEZONE_ABBR_MAP.put("BRST", -120);
        TIMEZONE_ABBR_MAP.put("BRT", -180);
        TIMEZONE_ABBR_MAP.put("BST", 60);
        TIMEZONE_ABBR_MAP.put("BTT", 360);
        TIMEZONE_ABBR_MAP.put("C", 180);
        TIMEZONE_ABBR_MAP.put("CAST", 480);
        TIMEZONE_ABBR_MAP.put("CAT", 120);
        TIMEZONE_ABBR_MAP.put("CCT", 390);
        TIMEZONE_ABBR_MAP.put("CDT", -300);
        TIMEZONE_ABBR_MAP.put("CEST", 120);
        TIMEZONE_ABBR_MAP.put("CET", 60);
        TIMEZONE_ABBR_MAP.put("CHADT", 825);
        TIMEZONE_ABBR_MAP.put("CHAST", 765);
        TIMEZONE_ABBR_MAP.put("CKT", -600);
        TIMEZONE_ABBR_MAP.put("CLST", -180);
        TIMEZONE_ABBR_MAP.put("CLT", -240);
        TIMEZONE_ABBR_MAP.put("COT", -300);
        TIMEZONE_ABBR_MAP.put("CST", -360);
        TIMEZONE_ABBR_MAP.put("CVT", -60);
        TIMEZONE_ABBR_MAP.put("CXT", 420);
        TIMEZONE_ABBR_MAP.put("ChST", 600);
        TIMEZONE_ABBR_MAP.put("D", 240);
        TIMEZONE_ABBR_MAP.put("DAVT", 420);
        TIMEZONE_ABBR_MAP.put("E", 300);
        TIMEZONE_ABBR_MAP.put("EASST", -300);
        TIMEZONE_ABBR_MAP.put("EAST", -360);
        TIMEZONE_ABBR_MAP.put("EAT", 180);
        TIMEZONE_ABBR_MAP.put("ECT", -300);
        TIMEZONE_ABBR_MAP.put("EDT", -240);
        TIMEZONE_ABBR_MAP.put("EEST", 180);
        TIMEZONE_ABBR_MAP.put("EET", 120);
        TIMEZONE_ABBR_MAP.put("EGST", 0);
        TIMEZONE_ABBR_MAP.put("EGT", -60);
        TIMEZONE_ABBR_MAP.put("EST", -300);
        TIMEZONE_ABBR_MAP.put("ET", -300);
        TIMEZONE_ABBR_MAP.put("F", 360);
        TIMEZONE_ABBR_MAP.put("FJST", 780);
        TIMEZONE_ABBR_MAP.put("FJT", 720);
        TIMEZONE_ABBR_MAP.put("FKST", -180);
        TIMEZONE_ABBR_MAP.put("FKT", -240);
        TIMEZONE_ABBR_MAP.put("FNT", -120);
        TIMEZONE_ABBR_MAP.put("G", 420);
        TIMEZONE_ABBR_MAP.put("GALT", -360);
        TIMEZONE_ABBR_MAP.put("GAMT", -540);
        TIMEZONE_ABBR_MAP.put("GET", 240);
        TIMEZONE_ABBR_MAP.put("GFT", -180);
        TIMEZONE_ABBR_MAP.put("GILT", 720);
        TIMEZONE_ABBR_MAP.put("GMT", 0);
        TIMEZONE_ABBR_MAP.put("GST", 240);
        TIMEZONE_ABBR_MAP.put("GYT", -240);
        TIMEZONE_ABBR_MAP.put("H", 480);
        TIMEZONE_ABBR_MAP.put("HAA", -180);
        TIMEZONE_ABBR_MAP.put("HAC", -300);
        TIMEZONE_ABBR_MAP.put("HADT", -540);
        TIMEZONE_ABBR_MAP.put("HAE", -240);
        TIMEZONE_ABBR_MAP.put("HAP", -420);
        TIMEZONE_ABBR_MAP.put("HAR", -360);
        TIMEZONE_ABBR_MAP.put("HAST", -600);
        TIMEZONE_ABBR_MAP.put("HAT", -90);
        TIMEZONE_ABBR_MAP.put("HAY", -480);
        TIMEZONE_ABBR_MAP.put("HKT", 480);
        TIMEZONE_ABBR_MAP.put("HLV", -210);
        TIMEZONE_ABBR_MAP.put("HNA", -240);
        TIMEZONE_ABBR_MAP.put("HNC", -360);
        TIMEZONE_ABBR_MAP.put("HNE", -300);
        TIMEZONE_ABBR_MAP.put("HNP", -480);
        TIMEZONE_ABBR_MAP.put("HNR", -420);
        TIMEZONE_ABBR_MAP.put("HNT", -150);
        TIMEZONE_ABBR_MAP.put("HNY", -540);
        TIMEZONE_ABBR_MAP.put("HOVT", 420);
        TIMEZONE_ABBR_MAP.put("I", 540);
        TIMEZONE_ABBR_MAP.put("ICT", 420);
        TIMEZONE_ABBR_MAP.put("IDT", 180);
        TIMEZONE_ABBR_MAP.put("IOT", 360);
        TIMEZONE_ABBR_MAP.put("IRDT", 270);
        TIMEZONE_ABBR_MAP.put("IRKST", 540);
        TIMEZONE_ABBR_MAP.put("IRKT", 540);
        TIMEZONE_ABBR_MAP.put("IRST", 210);
        TIMEZONE_ABBR_MAP.put("IST", 60);
        TIMEZONE_ABBR_MAP.put("JST", 540);
        TIMEZONE_ABBR_MAP.put("K", 600);
        TIMEZONE_ABBR_MAP.put("KGT", 360);
        TIMEZONE_ABBR_MAP.put("KRAST", 480);
        TIMEZONE_ABBR_MAP.put("KRAT", 480);
        TIMEZONE_ABBR_MAP.put("KST", 540);
        TIMEZONE_ABBR_MAP.put("KUYT", 240);
        TIMEZONE_ABBR_MAP.put("L", 660);
        TIMEZONE_ABBR_MAP.put("LHDT", 660);
        TIMEZONE_ABBR_MAP.put("LHST", 630);
        TIMEZONE_ABBR_MAP.put("LINT", 840);
        TIMEZONE_ABBR_MAP.put("M", 720);
        TIMEZONE_ABBR_MAP.put("MAGST", 720);
        TIMEZONE_ABBR_MAP.put("MAGT", 720);
        TIMEZONE_ABBR_MAP.put("MART", -510);
        TIMEZONE_ABBR_MAP.put("MAWT", 300);
        TIMEZONE_ABBR_MAP.put("MDT", -360);
        TIMEZONE_ABBR_MAP.put("MESZ", 120);
        TIMEZONE_ABBR_MAP.put("MEZ", 60);
        TIMEZONE_ABBR_MAP.put("MHT", 720);
        TIMEZONE_ABBR_MAP.put("MMT", 390);
        TIMEZONE_ABBR_MAP.put("MSD", 240);
        TIMEZONE_ABBR_MAP.put("MSK", 240);
        TIMEZONE_ABBR_MAP.put("MST", -420);
        TIMEZONE_ABBR_MAP.put("MUT", 240);
        TIMEZONE_ABBR_MAP.put("MVT", 300);
        TIMEZONE_ABBR_MAP.put("MYT", 480);
        TIMEZONE_ABBR_MAP.put("N", -60);
        TIMEZONE_ABBR_MAP.put("NCT", 660);
        TIMEZONE_ABBR_MAP.put("NDT", -90);
        TIMEZONE_ABBR_MAP.put("NFT", 690);
        TIMEZONE_ABBR_MAP.put("NOVST", 420);
        TIMEZONE_ABBR_MAP.put("NOVT", 360);
        TIMEZONE_ABBR_MAP.put("NPT", 345);
        TIMEZONE_ABBR_MAP.put("NST", -150);
        TIMEZONE_ABBR_MAP.put("NUT", -660);
        TIMEZONE_ABBR_MAP.put("NZDT", 780);
        TIMEZONE_ABBR_MAP.put("NZST", 720);
        TIMEZONE_ABBR_MAP.put("O", -120);
        TIMEZONE_ABBR_MAP.put("OMSST", 420);
        TIMEZONE_ABBR_MAP.put("OMST", 420);
        TIMEZONE_ABBR_MAP.put("P", -180);
        TIMEZONE_ABBR_MAP.put("PDT", -420);
        TIMEZONE_ABBR_MAP.put("PET", -300);
        TIMEZONE_ABBR_MAP.put("PETST", 720);
        TIMEZONE_ABBR_MAP.put("PETT", 720);
        TIMEZONE_ABBR_MAP.put("PGT", 600);
        TIMEZONE_ABBR_MAP.put("PHOT", 780);
        TIMEZONE_ABBR_MAP.put("PHT", 480);
        TIMEZONE_ABBR_MAP.put("PKT", 300);
        TIMEZONE_ABBR_MAP.put("PMDT", -120);
        TIMEZONE_ABBR_MAP.put("PMST", -180);
        TIMEZONE_ABBR_MAP.put("PONT", 660);
        TIMEZONE_ABBR_MAP.put("PST", -480);
        TIMEZONE_ABBR_MAP.put("PT", -480);
        TIMEZONE_ABBR_MAP.put("PWT", 540);
        TIMEZONE_ABBR_MAP.put("PYST", -180);
        TIMEZONE_ABBR_MAP.put("PYT", -240);
        TIMEZONE_ABBR_MAP.put("Q", -240);
        TIMEZONE_ABBR_MAP.put("R", -300);
        TIMEZONE_ABBR_MAP.put("RET", 240);
        TIMEZONE_ABBR_MAP.put("S", -360);
        TIMEZONE_ABBR_MAP.put("SAMT", 240);
        TIMEZONE_ABBR_MAP.put("SAST", 120);
        TIMEZONE_ABBR_MAP.put("SBT", 660);
        TIMEZONE_ABBR_MAP.put("SCT", 240);
        TIMEZONE_ABBR_MAP.put("SGT", 480);
        TIMEZONE_ABBR_MAP.put("SRT", -180);
        TIMEZONE_ABBR_MAP.put("SST", -660);
        TIMEZONE_ABBR_MAP.put("T", -420);
        TIMEZONE_ABBR_MAP.put("TAHT", -600);
        TIMEZONE_ABBR_MAP.put("TFT", 300);
        TIMEZONE_ABBR_MAP.put("TJT", 300);
        TIMEZONE_ABBR_MAP.put("TKT", 780);
        TIMEZONE_ABBR_MAP.put("TLT", 540);
        TIMEZONE_ABBR_MAP.put("TMT", 300);
        TIMEZONE_ABBR_MAP.put("TVT", 720);
        TIMEZONE_ABBR_MAP.put("U", -480);
        TIMEZONE_ABBR_MAP.put("ULAT", 480);
        TIMEZONE_ABBR_MAP.put("UTC", 0);
        TIMEZONE_ABBR_MAP.put("UYST", -120);
        TIMEZONE_ABBR_MAP.put("UYT", -180);
        TIMEZONE_ABBR_MAP.put("UZT", 300);
        TIMEZONE_ABBR_MAP.put("V", -540);
        TIMEZONE_ABBR_MAP.put("VET", -210);
        TIMEZONE_ABBR_MAP.put("VLAST", 660);
        TIMEZONE_ABBR_MAP.put("VLAT", 660);
        TIMEZONE_ABBR_MAP.put("VUT", 660);
        TIMEZONE_ABBR_MAP.put("W", -600);
        TIMEZONE_ABBR_MAP.put("WAST", 120);
        TIMEZONE_ABBR_MAP.put("WAT", 60);
        TIMEZONE_ABBR_MAP.put("WEST", 60);
        TIMEZONE_ABBR_MAP.put("WESZ", 60);
        TIMEZONE_ABBR_MAP.put("WET", 0);
        TIMEZONE_ABBR_MAP.put("WEZ", 0);
        TIMEZONE_ABBR_MAP.put("WFT", 720);
        TIMEZONE_ABBR_MAP.put("WGST", -120);
        TIMEZONE_ABBR_MAP.put("WGT", -180);
        TIMEZONE_ABBR_MAP.put("WIB", 420);
        TIMEZONE_ABBR_MAP.put("WIT", 540);
        TIMEZONE_ABBR_MAP.put("WITA", 480);
        TIMEZONE_ABBR_MAP.put("WST", 780);
        TIMEZONE_ABBR_MAP.put("WT", 0);
        TIMEZONE_ABBR_MAP.put("X", -660);
        TIMEZONE_ABBR_MAP.put("Y", -720);
        TIMEZONE_ABBR_MAP.put("YAKST", 600);
        TIMEZONE_ABBR_MAP.put("YAKT", 600);
        TIMEZONE_ABBR_MAP.put("YAPT", 600);
        TIMEZONE_ABBR_MAP.put("YEKST", 360);
        TIMEZONE_ABBR_MAP.put("YEKT", 360);
        TIMEZONE_ABBR_MAP.put("Z", 0);
    }
} public interface Refiner {
    
    List<ParsedResult> refine(List<ParsedResult> results, String text, ChronoOption options);
} public class ENMergeDateAndTimeRefiner extends RefinerAbstract {

    
    protected static boolean isDateOnly(ParsedResult result){
        return !result.start.isCertain(Components.Hour);
    }
    
    protected static boolean isTimeOnly(ParsedResult result){
        return !result.start.isCertain(Components.DayOfMonth) && !result.start.isCertain(Components.DayOfWeek);
    }
    
    protected static boolean ableToMerge(String text, ParsedResult prevResult, ParsedResult curResult){
        
        try{
            Pattern allowedPattern = Pattern.compile("\\s*(T|at|on|of|,|-)?\\s*", Pattern.CASE_INSENSITIVE);
            String  textBetween = text.substring(prevResult.index + prevResult.text.length(), curResult.index);
            return allowedPattern.matcher(textBetween).matches();
            
        }catch(Exception ex){ }
        
        return false;
    }
    
    protected static ParsedResult mergeResult(String text, ParsedResult dateResult, ParsedResult timeResult){
        
        ParsedDateComponent beginDate = dateResult.start;
        ParsedDateComponent beginTime = timeResult.start;
        
        ParsedDateComponent beginDateTime = new ParsedDateComponent(beginDate);
        beginDateTime.assign(Components.Hour, beginTime.get(Components.Hour));
        beginDateTime.assign(Components.Minute, beginTime.get(Components.Minute));
        beginDateTime.assign(Components.Second, beginTime.get(Components.Second));
        
        if (beginTime.isCertain(Components.Meridiem)) 
            beginDateTime.assign(Components.Meridiem, beginTime.get(Components.Meridiem));
        else if (beginTime.get(Components.Meridiem) != null)
            beginDateTime.imply(Components.Meridiem, beginTime.get(Components.Meridiem));
        
        dateResult.start = beginDateTime;
        
        if (dateResult.end != null || timeResult.end != null) {
            
            ParsedDateComponent endDate   = dateResult.end == null ? dateResult.start : dateResult.end;            
            ParsedDateComponent endTime   = timeResult.end == null ? timeResult.start : timeResult.end;

            ParsedDateComponent endDateTime = new ParsedDateComponent(endDate);
            endDateTime.assign(Components.Hour, endTime.get(Components.Hour));
            endDateTime.assign(Components.Minute, endTime.get(Components.Minute));
            endDateTime.assign(Components.Second, endTime.get(Components.Second));
            
            if (endTime.isCertain(Components.Meridiem)) 
        	endDateTime.assign(Components.Meridiem, endTime.get(Components.Meridiem));
            else if (beginTime.get(Components.Meridiem) != null)
        	endDateTime.imply(Components.Meridiem, endTime.get(Components.Meridiem));
            
            dateResult.end = endDateTime;
        }
        
        int startIndex = Math.min(dateResult.index, timeResult.index);
        int endIndex = Math.max(
                dateResult.index + dateResult.text.length(), 
                timeResult.index + timeResult.text.length());
        
        dateResult.index = startIndex;
        dateResult.text  = text.substring(startIndex, endIndex);
        dateResult.tags.addAll(timeResult.tags);
        dateResult.tags.add(ENMergeDateAndTimeRefiner.class.getName());
        return dateResult;
    }
    
    
    @Override
    public List<ParsedResult> refine(List<ParsedResult> results, String text, ChronoOption options) {
        
        if (results.size() < 2) return results;
        
        List<ParsedResult> mergedResult = new ArrayList<ParsedResult>();
        
        
        ParsedResult currResult = null;
        ParsedResult prevResult = null;
        for (int i=1; i<results.size(); i++){
            
            currResult = results.get(i);
            prevResult = results.get(i-1);
            
            if (isDateOnly(prevResult) && isTimeOnly(currResult) 
                    && ableToMerge(text, prevResult, currResult)) {
                
                prevResult = mergeResult(text, prevResult, currResult);
                currResult = null;
                i += 1;
                
            } else if (isDateOnly(currResult) && isTimeOnly(prevResult)
                    && ableToMerge(text, prevResult, currResult)) {
                
                prevResult = mergeResult(text, currResult, prevResult);
                currResult = null;
                i += 1;
            }
            
            mergedResult.add(prevResult);
        }
        
        if (currResult != null) {
            mergedResult.add(currResult);
        }

        return mergedResult;
    }

} public class ENMergeDateRangeRefiner extends RefinerAbstract {
    
    protected static boolean ableToMerge(String text, ParsedResult prevResult, ParsedResult curResult){
        
        try{
            Pattern allowedPattern = Pattern.compile("\\s*(and|to|-|ー)?\\s*", Pattern.CASE_INSENSITIVE);
            String  textBetween = text.substring(prevResult.index + prevResult.text.length(), curResult.index);
            
            return allowedPattern.matcher(textBetween).matches();
        
        }catch(StringIndexOutOfBoundsException ex){
            
        }
        
        return false;
    }
    
    protected static ParsedResult mergeResult(String text, ParsedResult fromResult, ParsedResult toResult){
        
        if (fromResult.start.date().after(toResult.start.date())) {
            ParsedResult tmp = fromResult;
            toResult = fromResult;
            fromResult = tmp;
        }
        
        fromResult.end = toResult.start;
        
        int startIndex = Math.min(fromResult.index, toResult.index);
        int endIndex = Math.max(
                fromResult.index + fromResult.text.length(), 
                toResult.index + toResult.text.length());
        
        fromResult.index = startIndex;
        fromResult.text  = text.substring(startIndex, endIndex);
        fromResult.tags.addAll(toResult.tags);
        fromResult.tags.add(ENMergeDateRangeRefiner.class.getName());
        return fromResult;
    }
    
    
    
    @Override
    public List<ParsedResult> refine(List<ParsedResult> results, String text, ChronoOption options) {
        
        if (results.size() < 2) return results;
        
        List<ParsedResult> mergedResult = new ArrayList<ParsedResult>();
        ParsedResult currResult = null;
        ParsedResult prevResult = null;
        
        for (int i=1; i<results.size(); i++){
            
            currResult = results.get(i);
            prevResult = results.get(i-1);
            
            if (prevResult.end == null && currResult.end == null 
                    && ableToMerge(text, prevResult, currResult)) {
                
                prevResult = mergeResult(text, prevResult, currResult);
                currResult = null;
                i += 1;
            } 
            
            mergedResult.add(prevResult);
        }
        
        if (currResult != null) {
            mergedResult.add(currResult);
        }

        return mergedResult;
    }
    
    
} public class ENMergeWeekdayRefiner extends RefinerAbstract {

    private static String WEEK_TAG = ENWeekExpressionParser.class.getName();
    private static String WEEK_DAY_TAG = ENDayOfWeekDateFormatParser.class.getName();

    private static Pattern ALLOWED_PATTERN_BETWEEN = Pattern.compile("\\s*('s|on)?\\s*", Pattern.CASE_INSENSITIVE);


    protected static boolean checkPatternBetween(String text, ParsedResult prevResult, ParsedResult curResult) {

        try {
            String textBetween = text.substring(prevResult.index + prevResult.text.length(), curResult.index);
            return ALLOWED_PATTERN_BETWEEN.matcher(textBetween).matches();

        } catch (StringIndexOutOfBoundsException ex) {

        }

        return false;
    }

    protected static boolean checkMergingCompatible(ParsedResult weekResult, ParsedResult weekDayResult) {

        if (weekResult.start.isCertain(Components.DayOfMonth) || weekResult.end != null)
            return false;

        if (weekDayResult.start.isCertain(Components.DayOfMonth))
            return false;

        if (weekDayResult.end != null && weekDayResult.start.isCertain(Components.DayOfMonth))
            return false;

        return true;
    }

    protected static ParsedResult mergeResult(String text, ParsedResult weekResult, ParsedResult weekDayResult) {

        ParsedResult mergedResult = new ParsedResult();
        mergedResult.tags.add(ENMergeWeekdayRefiner.class.getName());
        mergedResult.tags.addAll(weekResult.tags);
        mergedResult.tags.addAll(weekDayResult.tags);


        int startIndex = Math.min(weekResult.index, weekDayResult.index);
        int endIndex = Math.max(
                weekResult.index + weekResult.text.length(),
                weekDayResult.index + weekDayResult.text.length());

        mergedResult.index = startIndex;
        mergedResult.text  = text.substring(startIndex, endIndex);

        Calendar calendar = Calendar.getInstance(Locale.ENGLISH);
        calendar.setTime(weekResult.start.date());
        calendar.set(Calendar.DAY_OF_WEEK, weekDayResult.start.get(Components.DayOfWeek));

        mergedResult.start.imply(Components.Year, calendar.get(Calendar.YEAR));
        mergedResult.start.imply(Components.Month, calendar.get(Calendar.MONTH) + 1);
        mergedResult.start.imply(Components.DayOfMonth, calendar.get(Calendar.DAY_OF_MONTH));
        mergedResult.start.assign(Components.DayOfWeek, calendar.get(Calendar.DAY_OF_WEEK));

        if (weekDayResult.end != null) {

            calendar.setTime(weekResult.start.date());
            calendar.set(Calendar.DAY_OF_WEEK, weekDayResult.end.get(Components.DayOfWeek));

            mergedResult.end = new ParsedDateComponent();
            mergedResult.end.imply(Components.Year, calendar.get(Calendar.YEAR));
            mergedResult.end.imply(Components.Month, calendar.get(Calendar.MONTH) + 1);
            mergedResult.end.imply(Components.DayOfMonth, calendar.get(Calendar.DAY_OF_MONTH));
            mergedResult.end.assign(Components.DayOfWeek, calendar.get(Calendar.DAY_OF_WEEK));
        }

        return mergedResult;
    }


    @Override
    public List<ParsedResult> refine(List<ParsedResult> results, String text, ChronoOption options) {

        if (results.size() < 2) return results;

        final List<ParsedResult> mergedResult = new ArrayList<ParsedResult>();
        ParsedResult currResult = null;
        ParsedResult prevResult = null;

        for (int i = 1; i < results.size(); i++) {

            currResult = results.get(i);
            prevResult = results.get(i - 1);

            ParsedResult weekResult = null;
            ParsedResult weekDayResult = null;

            if (prevResult.tags.contains(WEEK_TAG) && currResult.tags.contains(WEEK_DAY_TAG)) {

                weekResult = prevResult;
                weekDayResult = currResult;

            } else if (prevResult.tags.contains(WEEK_DAY_TAG) && currResult.tags.contains(WEEK_TAG)) {

                weekResult = currResult;
                weekDayResult = prevResult;
            }

            if (weekResult != null && weekDayResult != null
                    && checkPatternBetween(text, prevResult, currResult)
                    && checkMergingCompatible(weekResult, weekDayResult)) {

                prevResult = mergeResult(text, weekResult, weekDayResult);
                currResult = null;
                i += 1;
            }

            mergedResult.add(prevResult);
        }

        if (currResult != null) {
            mergedResult.add(currResult);
        }

        return mergedResult;
    }


} public class ENRemoveOverlapRefiner extends RefinerAbstract {
    
    @Override
    public List<ParsedResult> refine(List<ParsedResult> results, String text, ChronoOption options) {
        
        if (results.size() < 2) return results;
        
        List<ParsedResult> filteredResults = new ArrayList<ParsedResult>();
        ParsedResult prevResult = results.get(0);
        
        for (int i=1; i<results.size(); i++){
            
            ParsedResult result = results.get(i);
            
            // If overlap, compare the length and discard the shorter one
            if (result.index < prevResult.index + prevResult.text.length()) {
                if (result.text.length() > prevResult.text.length()){
                    prevResult = result;
                }
                
            } else {
                filteredResults.add(prevResult);
                prevResult = result;
            }
        }
        
        // The last one
        if (prevResult != null) {
            filteredResults.add(prevResult);
        }
        
        return filteredResults;
    }
    
    
}
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.wanasit.chrono.filter.LowProbabilityFormatFilter;
import com.wanasit.chrono.filter.PrefixCheckFilter;
import com.wanasit.chrono.parser.Parser;
import com.wanasit.chrono.parser.en.*;
import com.wanasit.chrono.parser.jp.*;
import com.wanasit.chrono.refiner.ExtractConcordanceRefiner;
import com.wanasit.chrono.refiner.ExtractTimezoneRefiner;
import com.wanasit.chrono.refiner.Refiner;
import com.wanasit.chrono.refiner.en.ENMergeDateAndTimeRefiner;
import com.wanasit.chrono.refiner.en.ENMergeDateRangeRefiner;
import com.wanasit.chrono.refiner.en.ENMergeWeekdayRefiner;
import com.wanasit.chrono.refiner.en.ENRemoveOverlapRefiner;
package com.wanasit.chrono;



public class ChronoOption {

    public static final ChronoOption sharedOptions = standardOptions();

    public final List<Parser> parsers = new ArrayList<Parser>();
    public final List<Refiner> refiners = new ArrayList<Refiner>();

    public final Map<String, Integer> timezoneMap = new HashMap<String, Integer>();
    public Integer timezoneOffset = null;

    private ChronoOption() {
        this.timezoneOffset = Calendar.getInstance().getTimeZone().getOffset(0);
    }

    public static ChronoOption standardOptions() {
        ChronoOption options = new ChronoOption();

        // All Parsers
        options.parsers.add(new ENInternationalStandardParser());
        options.parsers.add(new ENMonthNameLittleEndianParser());
        options.parsers.add(new ENMonthNameMiddleEndianParser());
        options.parsers.add(new ENSlashBigEndianDateFormatParser());
        options.parsers.add(new ENSlashDateFormatParser());
        options.parsers.add(new ENTimeDeadlineFormatParser());
        options.parsers.add(new ENTimeAgoFormatParser());
        options.parsers.add(new ENTimeExpressionParser());
        options.parsers.add(new JPStandartDateFormatParser());
        options.parsers.add(new JPDayOfWeekDateFormatParser());
        options.parsers.add(new JPDateAgoFormatParser());
        options.parsers.add(new JPTimeExpressionParser());

        // Standard Pipeline
        options.refiners.add(new PrefixCheckFilter());
        options.refiners.add(new ENRemoveOverlapRefiner());
        options.refiners.add(new ENMergeDateAndTimeRefiner());
        options.refiners.add(new ENMergeDateRangeRefiner());