Java String Parsing and Evaluating - java

All,
I am in the process or rewriting some code that I wrote a while back. The objective of the code was to calcualte a date and time based on a string in the following formats:
DayStart+2Hour+1Day-2Minutes
NOW+20Day
MonthStart+1Month
Which would take the start of the day (in local time), e.g. 2011-09-15 00:00:00 BST (2011-09-15 23:00 GMT) then add 2 hours, add 1 day, and subtract 2 minutes.
The implementation is in Java and the original algorithm was pretty basic. It iterated through each character in the string and appended to a buffer. The buffer was then checked to see if it ended with the strings I was looking (date specifier e.g MINUTE, HOUR, DAYSTART, etc.) for then extracted the number and added to an ArrayList where DateOffset was a simple class with a int and String which was date specifier. Here is some sample code:
// hard coded for sample
String s = "DayStart+2Hour+1Day-2Minutes";
StringBuilder sbBuffer = new StringBuilder();
String buffer;
// iterate through date string
for (char c : s.toCharArray()) {
sbBuffer.append(c);
buffer = sbBuffer.toString();
// check to see the end of the buffer string is what we expect
if (buffer.endsWith("DAYSTART")) {
offsets.add(new DateOffset(0, "DAYSTART"));
sbBuffer = new StringBuilder();
} else if (buffer.endsWith("DAY") && buffer.length() > 3) {
String numberStringPart = buffer.substring(0, buffer.length() - 3);
numberStringPart = numberStringPart.replaceAll("[+]", "").trim(); // need as parseInt does not like the +.
offsets.add(new DateOffset(Integer.parseInt(numberStringPart), "DAY"));
sbBuffer = new StringBuilder();
} ... and so on ...
else {
}
}
After the string was parsed I iterated through ArrayList to calculate my datetime.
The problem with the above is probably not efficient although we have experienced no problems. It also does not pick up any errors so you could enter DayStart+2GKGKER.
I'm just trying to come up with some fresh and neat ideas on what to use to rewrite it. I have done a little regex but not too sure if this would be the best route.
Any thoughts?
Thanks,
Andez

Define a grammar for your expressions. Take a look at the ANTLR framework to help you construct a grammar and process your expressions.

Woohoo, that was fun! Thank you! :-)
public class DateExpressions {
private Map<String, Date> dateVariables;
private Map<String, Integer> temporalUnits;
private Map<Character, Integer> temporalOperations;
public static DateExpressions createInstance() {
DateExpressions de = new DateExpressions();
Calendar c = Calendar.getInstance();
de.setVariable("NOW", c.getTime());
c.set(Calendar.HOUR_OF_DAY, 0);
c.set(Calendar.MINUTE, 0);
c.set(Calendar.SECOND, 0);
c.set(Calendar.MILLISECOND, 0);
de.setVariable("DayStart", c.getTime());
c.set(Calendar.DAY_OF_MONTH, 1);
de.setVariable("MonthStart", c.getTime());
return de;
}
public DateExpressions() {
this.dateVariables = new HashMap<String, Date>();
this.temporalUnits = new HashMap<String, Integer>();
this.temporalUnits.put("Second", Calendar.SECOND);
this.temporalUnits.put("Minute", Calendar.MINUTE);
this.temporalUnits.put("Hour", Calendar.HOUR_OF_DAY);
this.temporalUnits.put("Day", Calendar.DATE);
this.temporalUnits.put("Month", Calendar.MONTH);
this.temporalUnits.put("Year", Calendar.YEAR);
this.temporalOperations = new HashMap<Character, Integer>();
this.temporalOperations.put('+', 1);
this.temporalOperations.put('-', -1);
}
public void setVariable(String key, Date value) {
this.dateVariables.put(key, value);
}
public Date parseExpression(String expr) throws IOException {
StringReader sr = new StringReader(expr);
String s;
int n;
char c;
int offset;
int unit;
int op = 1;
Calendar base = null;
StringBuilder sb1 = new StringBuilder();
StringBuilder sb2 = new StringBuilder();
while ((n = sr.read()) != -1) {
c = (char) n;
if (base == null && temporalOperations.containsKey(c)) {
s = sb2.toString();
if (!dateVariables.containsKey(s)) {
throw new IOException("Unknown variable '" + s + "' used");
}
base = Calendar.getInstance();
base.setTime(dateVariables.get(sb2.toString()));
op = temporalOperations.get(c);
sb1.setLength(0);
sb2.setLength(0);
} else if (temporalOperations.containsKey(c)) {
if (!temporalUnits.containsKey(sb2.toString())) {
throw new IOException(
"Parse error: unknown temporal unit used '"
+ sb2.toString() + "'");
}
offset = Integer.parseInt(sb1.toString());
unit = temporalUnits.get(sb2.toString());
base.add(unit, op * offset);
op = temporalOperations.get(c);
sb1.setLength(0);
sb2.setLength(0);
} else if (Character.isDigit(c)) {
sb1.append(c);
} else {
sb2.append(c);
}
}
if (!temporalUnits.containsKey(sb2.toString())) {
throw new IOException("Parse error: unknown temporal unit used '"
+ sb2.toString() + "'");
}
offset = Integer.parseInt(sb1.toString());
unit = temporalUnits.get(sb2.toString());
base.add(unit, op * offset);
return base.getTime();
}
public static void main(String[] args) throws IOException {
DateExpressions de = DateExpressions.createInstance();
System.out.println(de.parseExpression("DayStart+2Hour+1Day-2Minute"));
System.out.println(de.parseExpression("NOW+20Day"));
System.out.println(de.parseExpression("MonthStart+1Month"));
}
}

If you're after rapid experimentation, sometimes a literate API combined with on the fly compilation is an easy way to go.
So, your example could look like (given appropriate static imports)
daystart().plus()
.hours(2).plus()
.days(1).minutes(2)
or even (given milliseconds as the basic units)
daystart() + hours(2) + days(1) - minutes(2)

Regex seems to be the best bet for such a scenario. Although, I'm puzzled why would you want to interpret strings in this manner, rather than having sophisticated APIs.

Related

Avoiding a particular check for YYYY-MM--dd format in date

I have below method in which different date patterns have been handled
below is the method in which different date formats have been handled now
now for the particulat format YYYY-MM-dd i don't want it to go for the check where we are prefixing 20 before in code please advise how can i skip that part lets say if the date pattern is YYYY-MM-dd then avoid the logic of prefixing 20 in front of year
below is my code
public java.util.Date extractDate(String dateStr, String dateType) {
String[] datePatternsOfUk = { "d-M-yy", "d-M-yyyy", "d/M/yy", "d/M/yyyy", "yyyy-MM-dd","dd-MM-yy", "dd-MMM-yy","dd-MMM-yyyy","dd-MM-yyyy",
"dd/MM/yy","dd/MMM/yy","dd/MMM/yyyy"};
String[] datePatternsOfUs = { "M-d-yy","MM-dd-yy","M/d/yy","MM/dd/yy", "MM/dd/yy", "MMM-dd-yy",
"MMM/dd/yy", "MMM-dd-yyyy", "MM-dd-yyyy", "MMM/dd/yyyy",
"MM/dd/yyyy" };
java.util.Date date = null;
String[] datePatterns = datePatternsOfUk;
if (dateType.equals("US")) {
datePatterns = datePatternsOfUs;
} else if (dateType.equals("UK")) {
datePatterns = datePatternsOfUk;
}
///******code should not go in this check where date pattern is YYYY-MM-dd
int p = dateStr.lastIndexOf("/");
if (p == -1) {
p = dateStr.lastIndexOf("-");
}
String firstSubstring = dateStr.substring(0, p + 1);
String secondSubstring = dateStr.substring(p + 1);
if (p != -1 && secondSubstring.length() <= 2) {
secondSubstring = Integer.toString(2000 + Integer.parseInt(secondSubstring));
dateStr = firstSubstring + secondSubstring;
}
///****************************************//
try {
date = DateUtils.parseDate(dateStr, datePatterns);
} catch (ParseException ex) {
logger.error("##$$$$$### Error in invoice inside extractDate method : ##$$$$$$#### "
+ ErrorUtility.getStackTraceForException(ex));
}
return date;
}
You could avoid trying any inappropriate pattern by checking if the string "looks like" the pattern before parsing with the pattern.
The general way to do this is:
String datePattern = "yyyy-MM-dd"; // for example
String input;
if (input.matches(datePattern.replaceAll("\\w", "\\d"))) {
// the input looks like the pattern
// in this example "dddd-dd-dd" where "d" is any digit
// so go ahead and try the parse
}
You can enhance this logic to add:
if (input.matches("\\d\\d\\D.*")) {
// then it only has a two digit year, so add "20" to the front
}
if (!dateStr.equals("YYYY-MM-dd")) {
// code
}

Java JsonObjectBuilder adding extra 'metadata' when added as child to JsonObjectBuilder

I have one JsonObjectBuilder that builds my response.
I have a for loop that loops 7 times, during each iteration it builds a new JsonObjectBuilder, adds key/value pairs, then this JsonObjectBuilder instance is added to the parent Builder for my response.
As I understand it, this method should build 7 nested JsonObjects in my response object.
private void addStoreHoursResponse(Map<String,Object> response, AppConfigHelper configHelper) throws IOException {
final String OPEN = "open";
final String CLOSE = "close";
final String NOTES = "notes";
JsonObject storeHours = configHelper.getStoreHours();
Calendar now = DateUtils.getEasternTimeZoneCalendar();
now.set(Calendar.SECOND, 0);
now.set(Calendar.MILLISECOND, 0);
JsonObjectBuilder responseBuilder = Json.createObjectBuilder();
String open, close, notes;
for (int i = 0; i < 7; i++) {
JsonObjectBuilder hoursBuilder = Json.createObjectBuilder();
HoursKey hoursKey = HoursKey.getHoursKey(now);
JsonObject hours = storeHours.getJsonObject(hoursKey.toString());
open = hours.isNull(OPEN) ? null : hours.getString(OPEN);
close = hours.isNull(CLOSE) ? null : hours.getString(CLOSE);
notes = hours.isNull(NOTES) ? null : hours.getString(NOTES);
if (open == null || close == null) {
hoursBuilder.add(OPEN, JsonValue.NULL);
hoursBuilder.add(CLOSE, JsonValue.NULL);
hoursBuilder.add(NOTES, JsonValue.NULL);
} else {
hoursBuilder.add(OPEN, DateUtils.getIsoString(setCalendarTime(now, open)));
hoursBuilder.add(CLOSE, DateUtils.getIsoString(setCalendarTime(now, close)));
hoursBuilder.add(NOTES, notes);
}
responseBuilder.add(DateUtils.getIsoString(now), hoursBuilder);
now.add(Calendar.DAY_OF_MONTH, 1);
}
response.put(STORE_HOURS, responseBuilder.build());
}
private Calendar setCalendarTime(Calendar calendar, String time) {
String[] timeArray = time.split(":");
int hour = Integer.parseInt(timeArray[0]);
int minute = Integer.parseInt(timeArray[1]);
calendar.set(Calendar.HOUR_OF_DAY, hour);
calendar.set(Calendar.MINUTE, minute);
return calendar;
}
My JsonResponse has the 7 JsonObjects, but they should look like the following...
"open" : ISO time string,
"close" : ISO time string,
"notes" : String value
I am getting this as a result, what am I doing wrong?

datetime period with optional months

I`m trying to build formatter in JodaTime to parse Period from strings like these:
year 1hour 90min
1year -60days 800min
1year +1months -1days +1hour -30min
I know I can build parser with PeriodFormatterBuilder in jodatime but with it I can`t parse first two examples
PeriodFormatter formatter = new PeriodFormatterBuilder()
.appendYears().appendSuffix("year", "years").appendSeparatorIfFieldsAfter(" ")
.appendMonths().appendSuffix("month", "months").appendSeparatorIfFieldsAfter(" ")
.appendDays().appendSuffix("day", "days").appendSeparatorIfFieldsAfter(" ")
.appendHours().appendSuffix("hour", "hours").appendSeparatorIfFieldsAfter(" ")
.appendMinutes().appendSuffix("min", "mins").appendSeparatorIfFieldsAfter(" ")
.appendSeconds().appendSuffix("sec", "secs")
.toFormatter();
Is there any way I can tell joda that tose fields are optional?
If you really wanna achieve this, you can do so by writing a custom parser. For this you will have to implement PeriodParser class and implement the parseInto() method.
#Override
public int parseInto(ReadWritablePeriod period, String periodStr,
int position, Locale locale) {
String tokens[] = periodStr.split(" ");
period.addYears(0);
period.addMonths(0);
period.addDays(0);
period.addHours(0);
period.addMinutes(0);
period.addSeconds(0);
for (String token : tokens) {
int count = 0;
if (token.contains("year")) {
String years = token.substring(0, token.indexOf("year"));
period.addYears(years.length() > 0 ? Integer.valueOf(years) : 0);
continue;
}
if (token.contains("hour")) {
period.addHours(Integer.valueOf(token.substring(0, token.indexOf("hour"))));
continue;
}
if (token.contains("min")) {
period.addMinutes(Integer.valueOf(token.substring(0, token.indexOf("min"))));
continue;
}
if (token.contains("months")) {
period.addMonths(Integer.valueOf(token.substring(0, token.indexOf("months"))));
continue;
}
if (token.contains("day")) {
period.addDays(Integer.valueOf(token.substring(0, token.indexOf("days"))));
continue;
}
}
return periodStr.length();
}
After that use the following code to create a formatter and parse the period.
PeriodFormatterBuilder builder = new PeriodFormatterBuilder();
PeriodFormatter formatter = builder.append(null, new MyParsePeriod()).toFormatter();

Check if String token contains string char and String number value

1)I need to check if String contains a String characters what will be the corect way how to do it ?
2) Are some ways how to corectly transform String to number and then compare theese two number s? Like String = "House":1234 is equal to "House":1234 but no to "house":123
Priview:
String token ="123"; False
String token = "ā123"; or other characters True utc.
if(isChars(token)){
Long value = toLong(token);
}
THANKS!
//EDIT
public BigDecimal eval() {
Stack<BigDecimal> stack = new Stack<BigDecimal>();
for (String token : getRPN()) {
if (operators.containsKey(token)) {
BigDecimal v1 = stack.pop();
BigDecimal v2 = stack.pop();
stack.push(operators.get(token).eval(v2, v1));
} else if (variables.containsKey(token)) {
stack.push(variables.get(token).round(mc));
} else if (functions.containsKey(token.toUpperCase())) {
Function f = functions.get(token.toUpperCase());
ArrayList<BigDecimal> p = new ArrayList<BigDecimal>(f.getNumParams());
for (int i = 0; i < f.numParams; i++) {
p.add(0, stack.pop());
}
BigDecimal fResult = f.eval(p);
stack.push(fResult);
} else if (isDate(token)) {
Long date = null;
try {
date = SU.sdf.parse(token).getTime();
} catch (ParseException e) {/* IGNORE! */
}
// mylog.pl("LONG DATE : "+new BigDecimal(date, mc));
stack.push(new BigDecimal(date, mc));
}//TODO HERE
else if (isChar(token)){
Long cha = toLong(token);
stack.push(new BigDecimal(cha, mc));
//TODO ENDS HERE
}
else {
// mylog.pl("Token : "+ token);
stack.push(new BigDecimal(token, mc));
}
}
return stack.pop().stripTrailingZeros();
}
Another way for determing whether string contains any chars is nice class StringUtils from apache-commons-lang library.
It contains several methods for analyzing string's contents. It seems that in your case you can use StringUtils.isAlphanumeric(CharSequence cs) or negation of StringUtils.isNumeric(CharSequence cs)'s result.
What about second part of your question, so I do not see here necessety of extracting numbers from string. You can compare strings "House":1234 and "house":123 using standard String.equals() method.
Long l;
try{
l = Long.parseLong(token);
} catch(NumberFormatException e){
//contains non-numeric character(s)
}
As for "transforming varchar into Long" - that sounds rather impossible, we do not have universally accepted way of doing that, and you did not provide one. However if I guess correctly that what you want is the number within the string disregarding the characters - you want regular expressions. The code you want could look like:
if (!StringUtils.isNumeric(token)){
String stripped = token.replaceAll("\\D","");
Long l = Long.parseLong(stripped);
}

adding Gregorian Calendar dates to an array and retrieving the date to print in a string

I am writing a credit card program. I want the program to use the current date every time the method is used to make a purchase and put the date into the array
private GregorianCalendar transDate;
public CreditCard(double amount,String storeName, GregorianCalendar transDate) {
this.amount=amount;
this.storeName=storeName;
transDate=new GregorianCalendar();
}
public void purchase(double amount, String storeName, GregorianCalendar date)throws Exception
{
if (numPurchases<purchases.length)
if (amount >0 )
if(amount+balance<=creditLimit)
if( GregorianCalendar.getInstance().getTimeInMillis()<=expDate.getTimeInMillis())
{
balance+=amount;
transDate=getTransDate();
purchases[numPurchases] = new CreditCard(amount, storeName,transDate);
numPurchases++;
}
else
{
throw new Exception("card expired");
}
else{
throw new Exception("insufficient credit");
}
else{
throw new Exception("invalid amount");
}
else{
throw new Exception("exceeded number of allowed purchases");
}
}
I would like to display the information in String info
info+="Purchases:\n";
for(int index=0;index<numPurchases;index++){
info+="["+(index+1)+"] ";
info+=transDate.get(Calendar.YEAR)+"\t";
info+= purchases[index].getStoreName()+"\t";
info+=(formatter.format(purchases[index].getPurchase()))+"\n" ;
}
how do I need to set up the code to use the current date and add it to the array and display it in the string
Why don't you use a List implementation instead of an array? You can override the toString method to print it the way you want.
final SimpleDateFormat formatter = new SimpleDateFormat("dd MM yyyy");
List<Calendar> dates = new ArrayList<Calendar>() {
private static final long serialVersionUID = -5079502477457556887L;
#Override
public String toString() {
Iterator<Calendar> i = iterator();
if (!i.hasNext())
return "[]";
StringBuilder sb = new StringBuilder();
sb.append('[');
for (;;) {
Calendar c = i.next();
sb.append(formatter.format(c.getTime()));
if (! i.hasNext())
return sb.append(']').toString();
sb.append(", ");
}
}
};
dates.add(Calendar.getInstance());
dates.add(Calendar.getInstance());
System.out.println(dates);
What does your getTransDate() function do? Ideally it should return the transDate variable of CreditCard object. To calculate transDate for a purchase, you are better off renaming the method to calculateTransDate() or something like that.
Once you have getTransDate() method returning the transDate, your info string can be :
info+="Purchases:\n";
for(int index=0;index<numPurchases;index++){
info+="["+(index+1)+"] ";
info+=purchases[index].getTransDate().get(Calendar.YEAR)+"\t";
info+= purchases[index].getStoreName()+"\t";
info+=(formatter.format(purchases[index].getPurchase()))+"\n"
}

Categories