package com.mdimension.jchronic;
import java.util.LinkedList;
import java.util.List;
import com.mdimension.jchronic.handlers.Handler;
import com.mdimension.jchronic.numerizer.Numerizer;
import com.mdimension.jchronic.repeaters.Repeater;
import com.mdimension.jchronic.tags.Grabber;
import com.mdimension.jchronic.tags.Ordinal;
import com.mdimension.jchronic.tags.Pointer;
import com.mdimension.jchronic.tags.Scalar;
import com.mdimension.jchronic.tags.Separator;
import com.mdimension.jchronic.tags.TimeZone;
import com.mdimension.jchronic.utils.Span;
import com.mdimension.jchronic.utils.Token;
@SuppressWarnings("nls")
public class AstridChronic {
public static final String VERSION = "0.2.3";
private AstridChronic() {
// DO NOTHING
}
public static Span parse(String text) {
return AstridChronic.parse(text, new Options());
}
/**
* Parses a string containing a natural language date or time. If the parser
* can find a date or time, either a Time or Chronic::Span will be returned
* (depending on the value of :guess). If no date or time can be found,
* +nil+ will be returned.
*
* Options are:
*
* [:context]
* :past or :future (defaults to :future)
*
* If your string represents a birthday, you can set :context to :past
* and if an ambiguous string is given, it will assume it is in the
* past. Specify :future or omit to set a future context.
*
* [:now]
* Time (defaults to Time.now)
*
* By setting :now to a Time, all computations will be based off
* of that time instead of Time.now
*
* [:guess]
* +true+ or +false+ (defaults to +true+)
*
* By default, the parser will guess a single point in time for the
* given date or time. If you'd rather have the entire time span returned,
* set :guess to +false+ and a Chronic::Span will be returned.
*
* [:ambiguous_time_range]
* Integer or :none (defaults to 6 (6am-6pm))
*
* If an Integer is given, ambiguous times (like 5:00) will be
* assumed to be within the range of that time in the AM to that time
* in the PM. For example, if you set it to 7, then the parser will
* look for the time between 7am and 7pm. In the case of 5:00, it would
* assume that means 5:00pm. If :none is given, no assumption
* will be made, and the first matching instance of that time will
* be used.
*/
@SuppressWarnings("unchecked")
public static Span parse(String text, Options options) {
// store now for later =)
//_now = options.getNow();
// put the text into a normal format to ease scanning
String normalizedText = AstridChronic.preNormalize(text);
// get base tokens for each word
List tokens = AstridChronic.baseTokenize(normalizedText);
try {
tokens = Repeater.scan(tokens, options);
} catch (Throwable e) {
throw new RuntimeException("Failed to scan tokens.", e);
}
tokens = Grabber.scan(tokens, options);
tokens = Pointer.scan(tokens, options);
tokens = Scalar.scan(tokens, options);
tokens = Ordinal.scan(tokens, options);
tokens = Separator.scan(tokens, options);
tokens = TimeZone.scan(tokens, options);
List taggedTokens = new LinkedList();
for (Token token : tokens) {
if (token.isTagged()) {
taggedTokens.add(token);
}
}
tokens = taggedTokens;
if (options.isDebug()) {
System.out.println("Chronic.parse: " + tokens);
}
Span span = Handler.tokensToSpan(tokens, options);
// guess a time within a span if required
if (options.isGuess()) {
span = guess(span);
}
return span;
}
/**
* Clean up the specified input text by stripping unwanted characters,
* converting idioms to their canonical form, converting number words
* to numbers (three => 3), and converting ordinal words to numeric
* ordinals (third => 3rd)
*/
protected static String preNormalize(String text) {
String normalizedText = text.toLowerCase();
normalizedText = Chronic.numericizeNumbers(normalizedText);
normalizedText = normalizedText.replaceAll("['\"\\.]", "");
normalizedText = normalizedText.replaceAll("([/\\-,@])", " $1 ");
normalizedText = normalizedText.replaceAll("\\btoday\\b", "this day");
normalizedText = normalizedText.replaceAll("\\btomm?orr?ow\\b", "next day");
normalizedText = normalizedText.replaceAll("\\byesterday\\b", "last day");
normalizedText = normalizedText.replaceAll("\\bnoon\\b", "12:00");
normalizedText = normalizedText.replaceAll("\\bmidnight\\b", "24:00");
normalizedText = normalizedText.replaceAll("\\bbefore now\\b", "past");
normalizedText = normalizedText.replaceAll("\\bnow\\b", "this second");
normalizedText = normalizedText.replaceAll("\\b(ago|before)\\b", "past");
normalizedText = normalizedText.replaceAll("\\bthis past\\b", "last");
normalizedText = normalizedText.replaceAll("\\bthis last\\b", "last");
normalizedText = normalizedText.replaceAll("\\b(?:in|during) the (morning)\\b", "$1");
normalizedText = normalizedText.replaceAll("\\b(?:in the|during the|at) (afternoon|evening|night)\\b", "$1");
normalizedText = normalizedText.replaceAll("\\btonight\\b", "this night");
normalizedText = normalizedText.replaceAll("(?=\\w)([ap]m|oclock)\\b", " $1");
normalizedText = normalizedText.replaceAll("\\b(hence|after|from)\\b", "future");
normalizedText = AstridChronic.numericizeOrdinals(normalizedText);
return normalizedText;
}
/**
* Convert number words to numbers (three => 3)
*/
protected static String numericizeNumbers(String text) {
return Numerizer.numerize(text);
}
/**
* Convert ordinal words to numeric ordinals (third => 3rd)
*/
protected static String numericizeOrdinals(String text) {
return text;
}
/**
* Split the text on spaces and convert each word into
* a Token
*/
protected static List baseTokenize(String text) {
String[] words = text.split(" ");
List tokens = new LinkedList();
for (String word : words) {
tokens.add(new Token(word));
}
return tokens;
}
/**
* Guess a specific time within the given span
*/
// DIFF: We return Span instead of Date
protected static Span guess(Span span) {
if (span == null) {
return null;
}
long guessValue;
if (span.getWidth() > 1) {
guessValue = span.getBegin() + (span.getWidth() / 2);
}
else {
guessValue = span.getBegin();
}
Span guess = new Span(guessValue, guessValue);
return guess;
}
}