at.spardat.enterprise.fmt.ADateFmtMediumSmart Maven / Gradle / Ivy
/*******************************************************************************
* Copyright (c) 2003, 2007 s IT Solutions AT Spardat GmbH .
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* s IT Solutions AT Spardat GmbH - initial API and implementation
*******************************************************************************/
// @(#) $Id: ADateFmtMediumSmart.java 8163 2011-07-12 15:50:39Z laslovd $
package at.spardat.enterprise.fmt;
import java.util.*;
import at.spardat.enterprise.util.DateUtil;
import at.spardat.enterprise.util.NumberUtil;
/**
* This class is a high speed implementation for parsing and formatting
* dates, like the MEDIUM format of DateFormat. A very restricted set
* of patterns is allowed. A pattern is a sequence of the following:
*
* d1 ... represents a day component without leading zero
* d2 ... represents a two digits day component
* m1 ... represents a month component witout leading zero
* m2 ... represents a two digits month component
* y2 ... represents a two digit year component
* y4 ... represents a four digit year component
*
* Other characters must be used to delimit the components, as long as they
* do not start with d, m or y. A typical pattern for the austrian local
* may be "d2.m2.y4". The pattern must consist of exactly one d, one m and
* one y component. The components must be separated by delimiters. Therefore,
* a pattern "d2m2y4" is illegal.
*/
public class ADateFmtMediumSmart extends ADateFmt implements Cloneable {
// the pattern provided at construction time
private String pattern_;
// the position of the day component in the pattern
private int dRank_ = -1;
// the position of the month component in the pattern
private int mRank_ = -1;
// the position of the year component in the pattern
private int yRank_ = -1;
// used for completing incomplete year specifications
protected int twoDigitYearOffset_ = -80;
// keys are Strings denoting Locales, values are ADateFmtMediumSmart-objects
private static final HashMap fmts_ = new HashMap();
private static final ADateFmtMediumSmart defaultFmt_ = new ADateFmtMediumSmart("d2.m2.y4", 0);
// initialize the separators_ HashMap
static {
fmts_.put ("cs", new ADateFmtMediumSmart("d2.m1.y4", 0));
fmts_.put ("cs_CZ", new ADateFmtMediumSmart("d2.m1.y4", 0));
fmts_.put ("de", new ADateFmtMediumSmart("d2.m2.y4", 0));
fmts_.put ("de_AT", new ADateFmtMediumSmart("d2.m2.y4", 0));
fmts_.put ("en", new ADateFmtMediumSmart("d2/m2/y4", 0));
fmts_.put ("en_GB", new ADateFmtMediumSmart("d2/m2/y4", 0));
fmts_.put ("en_US", new ADateFmtMediumSmart("m2/d2/y4", 0));
fmts_.put ("hr", new ADateFmtMediumSmart("y4.m2.d2", 0));
fmts_.put ("hr_HR", new ADateFmtMediumSmart("y4.m2.d2", 0));
fmts_.put ("hu", new ADateFmtMediumSmart("y4.m2.d2.", 0));
fmts_.put ("hu_HU", new ADateFmtMediumSmart("y4.m2.d2.", 0));
fmts_.put ("sk", new ADateFmtMediumSmart("d2.m1.y4", 0));
fmts_.put ("sk_SK", new ADateFmtMediumSmart("d2.m1.y4", 0));
}
/**
* Returns a MEDIUM format object for a particular Locale.
*
* @param l the Locale
* @param style may be MANDATORY
* @return the non null format object.
*/
static ADateFmtMediumSmart getInstance (Locale l, int style) {
/**
* Look up the HashTable to get a template-object
*/
String locStr = l.toString();
ADateFmtMediumSmart result = (ADateFmtMediumSmart) fmts_.get(locStr);
if (result == null) {
int iUnder = locStr.lastIndexOf('_');
if (iUnder != -1) {
locStr = locStr.substring(0, iUnder);
result = (ADateFmtMediumSmart) fmts_.get(locStr);
if (result == null) {
iUnder = locStr.lastIndexOf('_');
if (iUnder != -1) {
locStr = locStr.substring(0, iUnder);
result = (ADateFmtMediumSmart) fmts_.get(locStr);
}
}
}
}
if (result == null) result = defaultFmt_;
/**
* Clone the found template object
*/
result = (ADateFmtMediumSmart)result.clone();
/**
* And set the right style
*/
result.style_ = style;
return result;
}
/**
* Constructs using a pattern as defined in the class header.
*
* @param pattern the pattern used
* @param style may be MANDATORY
* @exception RuntimeException on wrong patterns.
*/
protected ADateFmtMediumSmart (String pattern, int style) {
style_ = style;
setPattern (pattern);
}
/**
* Sets the pattern as defined in the class header.
*
* @param pattern the pattern to set
* @exception RuntimeException on wrong patterns
*/
public void setPattern (String pattern) {
// analyse the pattern
pattern_ = pattern;
dRank_ = -1; mRank_ = -1; yRank_ = -1;
int rank = 0;
for (int i=0, len=pattern.length(); i=0; i--) if (pattern_.charAt(i) == aChar) return true;
return false;
}
/**
* @see at.spardat.enterprise.fmt.IFmt#isOneWay()
*/
public boolean isOneWay() {
return false;
}
/**
* @see at.spardat.enterprise.fmt.IFmt#maxLenOfExternal()
*/
public int maxLenOfExternal() {
return pattern_.length() + 2; // adjust for 4 digit years
}
/**
* The parse method accepts the following inputs:
*
* - '0' ... zero stands for today
*
- '[-+] number' ... a signed number stands for today plus/minus the provided number of days.
*
- 'number' ... a number on its own represents the day component of the current month in the
* current year.
*
- 'number separators number' ... if only to components are given, they are taken to be
* month and day. The current year is added.
*
- 'number separators number separators number' ... the three numbers are considered to be the
* day, month and year components following the order defined in the pattern.
*
* The separators between the numbers need not match the separators provided in the patterns.
*
* Some rules apply to parsing the year component. If this componenent consists of at least 3 digits,
* it is taken as year itself, i.e., 002 means the year 0002. If the year component consists of 2 or 1 digit(s),
* it is thought of a year without century and a century using a sliding window approach is set down.
* The century is fixed in a way that the resulting year lies in the interval
* (currentYear+twoDigitYearOffset_, currentYear+twoDigitYearOffset_+100]
*
* @see at.spardat.enterprise.fmt.IFmt#parse(String)
*/
public String parse (String external) throws AParseException {
String internal = parse2 (external);
checkMandatory (internal);
checkDateRange (internal);
return internal;
}
/**
* Does the stuff described in method parse, without the mandatory-check.
*/
private String parse2 (String external) throws AParseException {
if (external == null) return "";
// check for spaces only
external = external.trim();
if (external.length() == 0) return "";
// needed very often for parsing in that what follows
char ch;
int index = 0;
int extLen = external.length();
// collects the result
DateUtil.DMY result = new DateUtil.DMY();
// special case: the string consists just of a number with an optional sign
char sign = 0;
int number = 0;
int numDigits = 0;
ch = external.charAt(index);
if (ch == '-') { sign = '-'; index++; }
if (ch == '+') { sign = '+'; index++; }
while (index < extLen) {
ch = external.charAt(index);
if (NumberUtil.isDigit(ch)) {
number = number*10 + ch-'0';
index++; numDigits++;
} else break;
}
// and the part after the number until the end is not numeric (which is ignored)
while (index < extLen) {
ch = external.charAt(index);
if (NumberUtil.isDigit(ch)) break;
else index++;
}
// ###
// did we parse until the end, i.e., we really have just one component.
// This is considerd to be a day component
if (index == extLen && numDigits >= 1) {
if (sign == '-') number *= -1;
// a single component is considered to be a day component
DateUtil.DMY today = DateUtil.getDMY(new GregorianCalendar());
result.d_ = today.d_;
result.m_ = today.m_;
result.y_ = today.y_;
if (number == 0) {
// a zero day component means today
} else {
// a non zero day component without sign is the day and possible a month and a year
if (sign == 0) {
if ( number > 1000000 ) { // e.g. 18052011 -> 18.05.2011,
result.y_ = number % 10000;
number /= 10000;
} else if ( number > 10000 ) { // e.g. 180511 -> 18.05.2011
result.y_ = number % 100 + 2000;
number /= 100;
}
if ( number > 100 ) { // e.g. 1805 -> 18.05.2011
result.m_ = number % 100;
number /= 100;
}
result.d_ = number;
}
else {
// a signed component means to roll the day forward or backwards
GregorianCalendar cal = new GregorianCalendar();
cal.add (Calendar.DATE, number);
result = DateUtil.getDMY (cal);
}
}
} else {
// the hard part: we split external up into components
int [] components = new int[3]; // the numeric value of the components
short [] lengths = new short[3];
int numC = 0;
index = 0;
// discover consecutive numeric strings
while (index < extLen) {
ch = external.charAt(index);
if (!NumberUtil.isDigit(ch)) { index++; continue; }
// here is a sequence of digits
while (index < extLen) {
ch = external.charAt(index);
if (!NumberUtil.isDigit(ch)) break;
if (numC >= 3) generalDateError();
components[numC] = components[numC] * 10 + ch - '0';
lengths[numC]++;
index++;
}
numC++;
}
// next, have a look at the components
if (numC <= 1) generalDateError();
else if (numC == 2) {
// two components are taken as day and month
DateUtil.DMY today = DateUtil.getDMY(new GregorianCalendar());
result.y_ = today.y_;
if (dRank_ < mRank_) {
result.d_ = components[0];
result.m_ = components[1];
} else {
result.d_ = components[1];
result.m_ = components[0];
}
} else {
// three components
result.d_ = components[dRank_];
result.m_ = components[mRank_];
result.y_ = components[yRank_];
// correct one or two digit years
if (lengths[yRank_] <= 2) {
// the year we have is just the year within a decade.
DateUtil.DMY today = DateUtil.getDMY(new GregorianCalendar());
int slidingDecadeBegin = today.y_ + twoDigitYearOffset_;
int slidingDecadeEnd = slidingDecadeBegin + 100;
if (result.y_ + slidingDecadeBegin/100*100 <= slidingDecadeBegin) result.y_ = result.y_ + slidingDecadeEnd/100*100;
else result.y_ = result.y_ + slidingDecadeBegin/100*100;
}
}
}
// check if result is valid
if (!DateUtil.isValid(result)) throw new FmtParseException ("ADateRange");
return DateUtil.DMY2Internal (result);
}
// convenience method to throw a FmtParseException
private void generalDateError () {
throw new FmtParseException ("ADateNotValid", format ("20031130"));
}
}