com.twelvemonkeys.util.TimeFormat Maven / Gradle / Ivy
Show all versions of common-lang Show documentation
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.util;
import com.twelvemonkeys.lang.StringUtil;
import java.text.FieldPosition;
import java.text.Format;
import java.text.ParsePosition;
import java.util.StringTokenizer;
import java.util.Vector;
/**
* Format for converting and parsing time.
*
* The format is expressed in a string as follows:
*
* - m (or any multiple of m's)
*
- the minutes part (padded with 0's, if number has less digits than
* the number of m's)
* m -> 0,1,...,59,60,61,...
* mm -> 00,01,...,59,60,61,...
*
- s or ss
*
- the seconds part (padded with 0's, if number has less digits than
* the number of s's)
* s -> 0,1,...,59
* ss -> 00,01,...,59
*
- S
*
- all seconds (including the ones above 59)
*
*
* May not handle all cases, and formats... ;-)
* Safest is: Always delimiters between the minutes (m) and seconds (s) part.
*
* Known bugs:
*
* The last character in the formatString is not escaped, while it should be.
* The first character after an escaped character is escaped while is shouldn't
* be.
*
* This is not a 100% compatible implementation of a java.text.Format.
*
* @see com.twelvemonkeys.util.Time
*
* @author Harald Kuhr
*/
// TODO:
// Move to com.twelvemonkeys.text?
// Milliseconds!
// Fix bugs.
public class TimeFormat extends Format {
final static String MINUTE = "m";
final static String SECOND = "s";
final static String TIME = "S";
final static String ESCAPE = "\\";
/**
* The default time format
*/
private final static TimeFormat DEFAULT_FORMAT = new TimeFormat("m:ss");
protected String formatString = null;
/**
* Main method for testing ONLY
*/
static void main(String[] argv) {
Time time = null;
TimeFormat in = null;
TimeFormat out = null;
if (argv.length >= 3) {
System.out.println("Creating out TimeFormat: \"" + argv[2] + "\"");
out = new TimeFormat(argv[2]);
}
if (argv.length >= 2) {
System.out.println("Creating in TimeFormat: \"" + argv[1] + "\"");
in = new TimeFormat(argv[1]);
}
else {
System.out.println("Using default format for in");
in = DEFAULT_FORMAT;
}
if (out == null)
out = in;
if (argv.length >= 1) {
System.out.println("Parsing: \"" + argv[0] + "\" with format \""
+ in.formatString + "\"");
time = in.parse(argv[0]);
}
else
time = new Time();
System.out.println("Time is \"" + out.format(time) +
"\" according to format \"" + out.formatString + "\"");
}
/**
* The formatter array.
*/
protected TimeFormatter[] formatter;
/**
* Creates a new TimeFormat with the given formatString,
*/
public TimeFormat(String pStr) {
formatString = pStr;
Vector formatter = new Vector();
StringTokenizer tok = new StringTokenizer(pStr, "\\msS", true);
String previous = null;
String current = null;
int previousCount = 0;
while (tok.hasMoreElements()) {
current = tok.nextToken();
if (previous != null && previous.equals(ESCAPE)) {
// Handle escaping of s, S or m
current = ((current != null) ? current : "")
+ (tok.hasMoreElements() ? tok.nextToken() : "");
previous = null;
previousCount = 0;
}
// Skip over first,
// or if current is the same, increase count, and try again
if (previous == null || previous.equals(current)) {
previousCount++;
previous = current;
}
else {
// Create new formatter for each part
if (previous.equals(MINUTE))
formatter.add(new MinutesFormatter(previousCount));
else if (previous.equals(SECOND))
formatter.add(new SecondsFormatter(previousCount));
else if (previous.equals(TIME))
formatter.add(new SecondsFormatter(-1));
else
formatter.add(new TextFormatter(previous));
previousCount = 1;
previous = current;
}
}
// Add new formatter for last part
if (previous != null) {
if (previous.equals(MINUTE))
formatter.add(new MinutesFormatter(previousCount));
else if (previous.equals(SECOND))
formatter.add(new SecondsFormatter(previousCount));
else if (previous.equals(TIME))
formatter.add(new SecondsFormatter(-1));
else
formatter.add(new TextFormatter(previous));
}
// Debug
/*
for (int i = 0; i < formatter.size(); i++) {
System.out.println("Formatter " + formatter.get(i).getClass()
+ ": length=" + ((TimeFormatter) formatter.get(i)).digits);
}
*/
this.formatter = (TimeFormatter[])
formatter.toArray(new TimeFormatter[formatter.size()]);
}
/**
* DUMMY IMPLEMENTATION!!
* Not locale specific.
*/
public static TimeFormat getInstance() {
return DEFAULT_FORMAT;
}
/** DUMMY IMPLEMENTATION!! */
/* Not locale specific
public static TimeFormat getInstance(Locale pLocale) {
return DEFAULT_FORMAT;
}
*/
/** DUMMY IMPLEMENTATION!! */
/* Not locale specific
public static Locale[] getAvailableLocales() {
return new Locale[] {Locale.getDefault()};
}
*/
/** Gets the format string. */
public String getFormatString() {
return formatString;
}
/** DUMMY IMPLEMENTATION!! */
public StringBuffer format(Object pObj, StringBuffer pToAppendTo,
FieldPosition pPos) {
if (!(pObj instanceof Time)) {
throw new IllegalArgumentException("Must be instance of " + Time.class);
}
return pToAppendTo.append(format(pObj));
}
/**
* Formats the the given time, using this format.
*/
public String format(Time pTime) {
StringBuilder buf = new StringBuilder();
for (int i = 0; i < formatter.length; i++) {
buf.append(formatter[i].format(pTime));
}
return buf.toString();
}
/** DUMMY IMPLEMENTATION!! */
public Object parseObject(String pStr, ParsePosition pStatus) {
Time t = parse(pStr);
pStatus.setIndex(pStr.length()); // Not 100%
return t;
}
/**
* Parses a Time, according to this format.
*
* Will bug on some formats. It's safest to always use delimiters between
* the minutes (m) and seconds (s) part.
*
*/
public Time parse(String pStr) {
Time time = new Time();
int sec = 0;
int min = 0;
int pos = 0;
int skip = 0;
boolean onlyUseSeconds = false;
for (int i = 0; (i < formatter.length)
&& (pos + skip < pStr.length()) ; i++) {
// Go to next offset
pos += skip;
if (formatter[i] instanceof MinutesFormatter) {
// Parse MINUTES
if ((i + 1) < formatter.length
&& formatter[i + 1] instanceof TextFormatter) {
// Skip until next format element
skip = pStr.indexOf(((TextFormatter) formatter[i + 1]).text, pos);
// Error in format, try parsing to end
if (skip < 0)
skip = pStr.length();
}
else if ((i + 1) >= formatter.length) {
// Skip until end of string
skip = pStr.length();
}
else {
// Hope this is correct...
skip = formatter[i].digits;
}
// May be first char
if (skip > pos)
min = Integer.parseInt(pStr.substring(pos, skip));
}
else if (formatter[i] instanceof SecondsFormatter) {
// Parse SECONDS
if (formatter[i].digits == -1) {
// Only seconds (or full TIME)
if ((i + 1) < formatter.length
&& formatter[i + 1] instanceof TextFormatter) {
// Skip until next format element
skip = pStr.indexOf(((TextFormatter) formatter[i + 1]).text, pos);
}
else if ((i + 1) >= formatter.length) {
// Skip until end of string
skip = pStr.length();
}
else {
// Cannot possibly know how long?
skip = 0;
continue;
}
// Get seconds
sec = Integer.parseInt(pStr.substring(pos, skip));
// System.out.println("Only seconds: " + sec);
onlyUseSeconds = true;
break;
}
else {
// Normal SECONDS
if ((i + 1) < formatter.length
&& formatter[i + 1] instanceof TextFormatter) {
// Skip until next format element
skip = pStr.indexOf(((TextFormatter) formatter[i + 1]).text, pos);
}
else if ((i + 1) >= formatter.length) {
// Skip until end of string
skip = pStr.length();
}
else {
skip = formatter[i].digits;
}
// Get seconds
sec = Integer.parseInt(pStr.substring(pos, skip));
}
}
else if (formatter[i] instanceof TextFormatter) {
skip = formatter[i].digits;
}
}
// Set the minutes part if we should
if (!onlyUseSeconds)
time.setMinutes(min);
// Set the seconds part
time.setSeconds(sec);
return time;
}
}
/**
* The base class of TimeFormatters
*/
abstract class TimeFormatter {
int digits = 0;
abstract String format(Time t);
}
/**
* Formats the seconds part of the Time
*/
class SecondsFormatter extends TimeFormatter {
SecondsFormatter(int pDigits) {
digits = pDigits;
}
String format(Time t) {
// Negative number of digits, means all seconds, no padding
if (digits < 0) {
return Integer.toString(t.getTime());
}
// If seconds is more than digits long, simply return it
if (t.getSeconds() >= Math.pow(10, digits)) {
return Integer.toString(t.getSeconds());
}
// Else return it with leading 0's
//return StringUtil.formatNumber(t.getSeconds(), digits);
return StringUtil.pad(String.valueOf(t.getSeconds()), digits, "0", true);
}
}
/**
* Formats the minutes part of the Time
*/
class MinutesFormatter extends TimeFormatter {
MinutesFormatter(int pDigits) {
digits = pDigits;
}
String format(Time t) {
// If minutes is more than digits long, simply return it
if (t.getMinutes() >= Math.pow(10, digits)) {
return Integer.toString(t.getMinutes());
}
// Else return it with leading 0's
//return StringUtil.formatNumber(t.getMinutes(), digits);
return StringUtil.pad(String.valueOf(t.getMinutes()), digits, "0", true);
}
}
/**
* Formats text constant part of the Time
*/
class TextFormatter extends TimeFormatter {
String text = null;
TextFormatter(String pText) {
text = pText;
// Just to be able to skip over
if (pText != null) {
digits = pText.length();
}
}
String format(Time t) {
// Simply return the text
return text;
}
}