diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/impl/unicode/CLDRHelper.java b/teavm-classlib/src/main/java/org/teavm/classlib/impl/unicode/CLDRHelper.java index 3dd704716..24bdf861e 100644 --- a/teavm-classlib/src/main/java/org/teavm/classlib/impl/unicode/CLDRHelper.java +++ b/teavm-classlib/src/main/java/org/teavm/classlib/impl/unicode/CLDRHelper.java @@ -108,6 +108,30 @@ public class CLDRHelper { @MetadataProvider(FirstDayOfWeekMetadataGenerator.class) public static native ResourceMap getFirstDayOfWeek(); + public static String resolveDateFormat(String language, String country) { + return resolveFormatSymbols(getDateFormatMap(), language, country); + } + + private static native ResourceMap getDateFormatMap(); + + public static String resolveFullDateFormat(String language, String country) { + return resolveFormatSymbols(getFullDateFormatMap(), language, country); + } + + private static native ResourceMap getFullDateFormatMap(); + + public static String resolveLongDateFormat(String language, String country) { + return resolveFormatSymbols(getLongDateFormatMap(), language, country); + } + + private static native ResourceMap getLongDateFormatMap(); + + public static String resolveShortDateFormat(String language, String country) { + return resolveFormatSymbols(getShortDateFormatMap(), language, country); + } + + private static native ResourceMap getShortDateFormatMap(); + public static String resolveNumberFormat(String language, String country) { return resolveFormatSymbols(getNumberFormatMap(), language, country); } diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/text/SimpleDatePatternParser.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/SimpleDatePatternParser.java deleted file mode 100644 index 00cabbb9a..000000000 --- a/teavm-classlib/src/main/java/org/teavm/classlib/java/text/SimpleDatePatternParser.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright 2014 Alexey Andreev. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.teavm.classlib.java.text; - -import java.util.ArrayList; -import java.util.List; - -/** - * - * @author Alexey Andreev - */ -class SimpleDatePatternParser { - private TDateFormatSymbols symbols; - private List elements = new ArrayList<>(); - private int index; - private String pattern; - - public SimpleDatePatternParser(TDateFormatSymbols symbols) { - this.symbols = symbols; - } - - public void parsePattern(String pattern) { - this.pattern = pattern; - for (index = 0; index < pattern.length(); ++index) { - char c = pattern.charAt(index); - switch (c) { - case '\'': { - ++index; - parseQuoted(); - break; - } - case 'G': - parseRepetitions(); - elements.add(new DateFormatElement.EraText(symbols)); - break; - case 'y': - break; - default: { - StringBuilder sb = new StringBuilder(); - while (index < pattern.length() && isControl(pattern.charAt(index))) { - sb.append(pattern.charAt(index++)); - } - break; - } - } - } - } - - private boolean isControl(char c) { - switch (c) { - case '\'': - case 'G': - case 'y': - case 'Y': - case 'M': - case 'w': - case 'W': - case 'D': - case 'd': - case 'F': - case 'E': - case 'u': - case 'a': - case 'H': - case 'k': - case 'K': - case 'h': - case 'm': - case 's': - case 'S': - case 'z': - case 'Z': - case 'X': - return true; - default: - return false; - } - } - - private void parseQuoted() { - StringBuilder sb = new StringBuilder(); - while (index < pattern.length()) { - char c = pattern.charAt(index++); - if (c == '\'') { - if (index < pattern.length() && pattern.charAt(index) == '\'') { - sb.append('\''); - ++index; - } else { - break; - } - } else { - sb.append(c); - } - } - elements.add(new DateFormatElement.ConstantText(sb.toString())); - } - - private int parseRepetitions() { - int count = 1; - char orig = pattern.charAt(index++); - while (index < pattern.length() && pattern.charAt(index) == orig) { - ++index; - ++count; - } - return count; - } -} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TDateFormat.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TDateFormat.java index f663eff15..e231e06e0 100644 --- a/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TDateFormat.java +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TDateFormat.java @@ -17,6 +17,7 @@ package org.teavm.classlib.java.text; +import org.teavm.classlib.impl.unicode.CLDRHelper; import org.teavm.classlib.java.util.*; public abstract class TDateFormat extends TFormat { @@ -58,9 +59,11 @@ public abstract class TDateFormat extends TFormat { return clone; } + // TODO: implement + /* @Override public boolean equals(Object object) { - /*if (this == object) { + if (this == object) { return true; } if (!(object instanceof DateFormat)) { @@ -70,10 +73,11 @@ public abstract class TDateFormat extends TFormat { return numberFormat.equals(dateFormat.numberFormat) && calendar.getFirstDayOfWeek() == dateFormat.calendar.getFirstDayOfWeek() && calendar.getMinimalDaysInFirstWeek() == dateFormat.calendar.getMinimalDaysInFirstWeek() && - calendar.isLenient() == dateFormat.calendar.isLenient();*/ - // TODO: implement + calendar.isLenient() == dateFormat.calendar.isLenient(); + return false; } + */ @Override public final StringBuffer format(Object object, StringBuffer buffer, TFieldPosition field) { @@ -110,11 +114,22 @@ public abstract class TDateFormat extends TFormat { } public final static TDateFormat getDateInstance(int style, TLocale locale) { - /*checkDateStyle(style); - com.ibm.icu.text.DateFormat icuFormat = com.ibm.icu.text.DateFormat.getDateInstance(style, locale); - return new SimpleDateFormat(locale, (com.ibm.icu.text.SimpleDateFormat) icuFormat);*/ - // TODO: implement - return null; + switch (style) { + case SHORT: + return new TSimpleDateFormat(CLDRHelper.resolveShortDateFormat( + locale.getLanguage(), locale.getCountry()), locale); + case MEDIUM: + return new TSimpleDateFormat(CLDRHelper.resolveDateFormat( + locale.getLanguage(), locale.getCountry()), locale); + case LONG: + return new TSimpleDateFormat(CLDRHelper.resolveLongDateFormat( + locale.getLanguage(), locale.getCountry()), locale); + case FULL: + return new TSimpleDateFormat(CLDRHelper.resolveFullDateFormat( + locale.getLanguage(), locale.getCountry()), locale); + default: + throw new IllegalArgumentException("Unknown style: " + style); + } } public final static TDateFormat getDateTimeInstance() { @@ -184,13 +199,14 @@ public abstract class TDateFormat extends TFormat { } - @Override + // TODO: implement + /*@Override public int hashCode() { - /*return calendar.getFirstDayOfWeek() + calendar.getMinimalDaysInFirstWeek() + - (calendar.isLenient() ? 1231 : 1237) + numberFormat.hashCode();*/ - // TODO: implement + return calendar.getFirstDayOfWeek() + calendar.getMinimalDaysInFirstWeek() + + (calendar.isLenient() ? 1231 : 1237) + numberFormat.hashCode(); + return 0; - } + }*/ public boolean isLenient() { return calendar.isLenient(); @@ -276,7 +292,6 @@ public abstract class TDateFormat extends TFormat { private static void checkTimeStyle(int style) { if (!(style == SHORT || style == MEDIUM || style == LONG || style == FULL || style == DEFAULT)) { - // text.0F=Illegal time style: {0} throw new IllegalArgumentException("Illegal time style: " + style); } } diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/text/DateFormatElement.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TDateFormatElement.java similarity index 80% rename from teavm-classlib/src/main/java/org/teavm/classlib/java/text/DateFormatElement.java rename to teavm-classlib/src/main/java/org/teavm/classlib/java/text/TDateFormatElement.java index 9f882fd6c..7455fdad1 100644 --- a/teavm-classlib/src/main/java/org/teavm/classlib/java/text/DateFormatElement.java +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TDateFormatElement.java @@ -22,7 +22,7 @@ import org.teavm.classlib.java.util.TGregorianCalendar; * * @author Alexey Andreev */ -abstract class DateFormatElement { +abstract class TDateFormatElement { public abstract void format(TCalendar date, StringBuffer buffer); public abstract void parse(String text, TCalendar date, TParsePosition position); @@ -49,7 +49,7 @@ abstract class DateFormatElement { return -1; } - public static class MonthText extends DateFormatElement { + public static class MonthText extends TDateFormatElement { String[] months; String[] shortMonths; boolean abbreviated; @@ -80,7 +80,7 @@ abstract class DateFormatElement { } } - public static class WeekdayText extends DateFormatElement { + public static class WeekdayText extends TDateFormatElement { String[] weeks; String[] shortWeeks; boolean abbreviated; @@ -111,7 +111,7 @@ abstract class DateFormatElement { } } - public static class EraText extends DateFormatElement { + public static class EraText extends TDateFormatElement { String[] eras; public EraText(TDateFormatSymbols symbols) { @@ -135,7 +135,7 @@ abstract class DateFormatElement { } } - public static class AmPmText extends DateFormatElement { + public static class AmPmText extends TDateFormatElement { String[] ampms; public AmPmText(TDateFormatSymbols symbols) { @@ -159,20 +159,18 @@ abstract class DateFormatElement { } } - public static class Numeric extends DateFormatElement { + public static class Numeric extends TDateFormatElement { private int field; private int length; - private int offset; - public Numeric(int field, int length, int offset) { + public Numeric(int field, int length) { this.field = field; this.length = length; - this.offset = offset; } @Override public void format(TCalendar date, StringBuffer buffer) { - int number = date.get(field) + offset; + int number = processBeforeFormat(date.get(field)); String str = Integer.toString(number); for (int i = str.length(); i < length; ++i) { buffer.append('0'); @@ -200,11 +198,70 @@ abstract class DateFormatElement { return; } position.setIndex(pos); - date.set(field, num - offset); + date.set(field, processAfterParse(num)); + } + + protected int processBeforeFormat(int num) { + return num; + } + + protected int processAfterParse(int num) { + return num; } } - public static class Year extends DateFormatElement { + public static class NumericMonth extends Numeric { + public NumericMonth(int length) { + super(TCalendar.MONTH, length); + } + + @Override + protected int processBeforeFormat(int num) { + return num + 1; + } + + @Override + protected int processAfterParse(int num) { + return num - 1; + } + } + + public static class NumericWeekday extends Numeric { + public NumericWeekday(int length) { + super(TCalendar.DAY_OF_WEEK, length); + } + + @Override + protected int processBeforeFormat(int num) { + return num == 1 ? 7 : num - 1; + } + + @Override + protected int processAfterParse(int num) { + return num == 7 ? 1 : num + 1; + } + } + + public static class NumericHour extends Numeric { + private int limit; + + public NumericHour(int field, int length, int limit) { + super(field, length); + this.limit = limit; + } + + @Override + protected int processBeforeFormat(int num) { + return num == 0 ? limit : num; + } + + @Override + protected int processAfterParse(int num) { + return num == limit ? 0 : num; + } + } + + public static class Year extends TDateFormatElement { private int field; public Year(int field) { @@ -255,7 +312,7 @@ abstract class DateFormatElement { } } - public static class ConstantText extends DateFormatElement { + public static class ConstantText extends TDateFormatElement { private String textConstant; public ConstantText(String textConstant) { diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TSimpleDateFormat.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TSimpleDateFormat.java index 7854dde11..9810a4ade 100644 --- a/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TSimpleDateFormat.java +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TSimpleDateFormat.java @@ -15,32 +15,69 @@ */ package org.teavm.classlib.java.text; +import org.teavm.classlib.impl.unicode.CLDRHelper; import org.teavm.classlib.java.util.TCalendar; import org.teavm.classlib.java.util.TDate; import org.teavm.classlib.java.util.TGregorianCalendar; +import org.teavm.classlib.java.util.TLocale; /** * * @author Alexey Andreev */ public class TSimpleDateFormat extends TDateFormat { - private DateFormatElement[] elements; + private TDateFormatSymbols dateFormatSymbols; + private TDateFormatElement[] elements; + private String pattern; + + public TSimpleDateFormat() { + this(getDefaultPattern()); + } + + private static String getDefaultPattern() { + TLocale locale = TLocale.getDefault(); + return CLDRHelper.resolveDateFormat(locale.getLanguage(), locale.getCountry()); + } + + public TSimpleDateFormat(String pattern) { + this(pattern, TLocale.getDefault()); + } + + public TSimpleDateFormat(String pattern, TLocale locale) { + this(pattern, new TDateFormatSymbols(locale)); + } + + public TSimpleDateFormat(String pattern, TDateFormatSymbols dateFormatSymbols) { + dateFormatSymbols = (TDateFormatSymbols)dateFormatSymbols.clone(); + applyPattern(pattern); + } @Override public StringBuffer format(TDate date, StringBuffer buffer, TFieldPosition field) { TCalendar calendar = new TGregorianCalendar(); calendar.setTime(date); - for (DateFormatElement element : elements) { + for (TDateFormatElement element : elements) { element.format(calendar, buffer); } return buffer; } + public void applyPattern(String pattern) { + this.pattern = pattern; + reparsePattern(); + } + + private void reparsePattern() { + TSimpleDatePatternParser parser = new TSimpleDatePatternParser(dateFormatSymbols); + parser.parsePattern(pattern); + elements = parser.getElements().toArray(new TDateFormatElement[0]); + } + @Override public TDate parse(String string, TParsePosition position) { TCalendar calendar = new TGregorianCalendar(); calendar.set(0, 0, 0, 0, 0, 0); - for (DateFormatElement element : elements) { + for (TDateFormatElement element : elements) { if (position.getIndex() > string.length()) { position.setErrorIndex(position.getErrorIndex()); return null; @@ -52,4 +89,25 @@ public class TSimpleDateFormat extends TDateFormat { } return calendar.getTime(); } + + @Override + public Object clone() { + TSimpleDateFormat copy = (TSimpleDateFormat)super.clone(); + copy.dateFormatSymbols = (TDateFormatSymbols)dateFormatSymbols.clone(); + copy.elements = elements.clone(); + return copy; + } + + public TDateFormatSymbols getDateFormatSymbols() { + return (TDateFormatSymbols)dateFormatSymbols.clone(); + } + + public void setDateFormatSymbols(TDateFormatSymbols newFormatSymbols) { + dateFormatSymbols = (TDateFormatSymbols)newFormatSymbols.clone(); + reparsePattern(); + } + + public String toPattern() { + return pattern; + } } diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TSimpleDatePatternParser.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TSimpleDatePatternParser.java new file mode 100644 index 000000000..a8e9e647d --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TSimpleDatePatternParser.java @@ -0,0 +1,194 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.classlib.java.text; + +import java.util.ArrayList; +import java.util.List; +import org.teavm.classlib.java.util.TCalendar; + +/** + * + * @author Alexey Andreev + */ +class TSimpleDatePatternParser { + private TDateFormatSymbols symbols; + private List elements = new ArrayList<>(); + private int index; + private String pattern; + + public TSimpleDatePatternParser(TDateFormatSymbols symbols) { + this.symbols = symbols; + } + + public List getElements() { + return elements; + } + + public void parsePattern(String pattern) { + elements.clear(); + this.pattern = pattern; + for (index = 0; index < pattern.length();) { + char c = pattern.charAt(index); + switch (c) { + case '\'': { + ++index; + parseQuoted(); + break; + } + case 'G': + parseRepetitions(); + elements.add(new TDateFormatElement.EraText(symbols)); + break; + case 'y': + case 'Y': { + int rep = parseRepetitions(); + if (rep <= 2) { + elements.add(new TDateFormatElement.Year(TCalendar.YEAR)); + } else { + elements.add(new TDateFormatElement.Numeric(TCalendar.YEAR, rep)); + } + break; + } + case 'M': + case 'L': { + int rep = parseRepetitions(); + if (rep <= 2) { + elements.add(new TDateFormatElement.NumericMonth(rep)); + } else { + elements.add(new TDateFormatElement.MonthText(symbols, rep == 3)); + } + break; + } + case 'w': { + int rep = parseRepetitions(); + elements.add(new TDateFormatElement.Numeric(TCalendar.WEEK_OF_YEAR, rep)); + break; + } + case 'W': { + int rep = parseRepetitions(); + elements.add(new TDateFormatElement.Numeric(TCalendar.WEEK_OF_MONTH, rep)); + break; + } + case 'D': { + int rep = parseRepetitions(); + elements.add(new TDateFormatElement.Numeric(TCalendar.DAY_OF_YEAR, rep)); + break; + } + case 'd': { + int rep = parseRepetitions(); + elements.add(new TDateFormatElement.Numeric(TCalendar.DAY_OF_MONTH, rep)); + break; + } + case 'F': { + int rep = parseRepetitions(); + elements.add(new TDateFormatElement.Numeric(TCalendar.DAY_OF_WEEK_IN_MONTH, rep)); + break; + } + case 'E': + case 'c': { + int rep = parseRepetitions(); + elements.add(new TDateFormatElement.WeekdayText(symbols, rep <= 3)); + break; + } + case 'u': { + int rep = parseRepetitions(); + elements.add(new TDateFormatElement.NumericWeekday(rep)); + break; + } + case 'a': { + parseRepetitions(); + elements.add(new TDateFormatElement.AmPmText(symbols)); + break; + } + case 'H': { + int rep = parseRepetitions(); + elements.add(new TDateFormatElement.Numeric(TCalendar.HOUR_OF_DAY, rep)); + break; + } + case 'k': { + int rep = parseRepetitions(); + elements.add(new TDateFormatElement.NumericHour(TCalendar.HOUR_OF_DAY, rep, 24)); + break; + } + case 'K': { + int rep = parseRepetitions(); + elements.add(new TDateFormatElement.Numeric(TCalendar.HOUR, rep)); + break; + } + case 'h': { + int rep = parseRepetitions(); + elements.add(new TDateFormatElement.NumericHour(TCalendar.HOUR, rep, 12)); + break; + } + case 'm': { + int rep = parseRepetitions(); + elements.add(new TDateFormatElement.Numeric(TCalendar.MINUTE, rep)); + break; + } + case 's': { + int rep = parseRepetitions(); + elements.add(new TDateFormatElement.Numeric(TCalendar.SECOND, rep)); + break; + } + case 'S': { + int rep = parseRepetitions(); + elements.add(new TDateFormatElement.Numeric(TCalendar.MILLISECOND, rep)); + break; + } + default: { + StringBuilder sb = new StringBuilder(); + while (index < pattern.length() && !isControl(pattern.charAt(index))) { + sb.append(pattern.charAt(index++)); + } + elements.add(new TDateFormatElement.ConstantText(sb.toString())); + break; + } + } + } + } + + private boolean isControl(char c) { + return c == '\'' || c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z'; + } + + private void parseQuoted() { + StringBuilder sb = new StringBuilder(); + while (index < pattern.length()) { + char c = pattern.charAt(index++); + if (c == '\'') { + if (index < pattern.length() && pattern.charAt(index) == '\'') { + sb.append('\''); + ++index; + } else { + break; + } + } else { + sb.append(c); + } + } + elements.add(new TDateFormatElement.ConstantText(sb.toString())); + } + + private int parseRepetitions() { + int count = 1; + char orig = pattern.charAt(index++); + while (index < pattern.length() && pattern.charAt(index) == orig) { + ++index; + ++count; + } + return count; + } +} diff --git a/teavm-classlib/src/test/java/org/teavm/classlib/java/text/SimpleDateTimeFormatTest.java b/teavm-classlib/src/test/java/org/teavm/classlib/java/text/SimpleDateTimeFormatTest.java new file mode 100644 index 000000000..be94da5fd --- /dev/null +++ b/teavm-classlib/src/test/java/org/teavm/classlib/java/text/SimpleDateTimeFormatTest.java @@ -0,0 +1,33 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.classlib.java.text; + +import static org.junit.Assert.*; +import java.text.SimpleDateFormat; +import java.util.Date; +import org.junit.Test; + +/** + * + * @author Alexey Andreev + */ +public class SimpleDateTimeFormatTest { + @Test + public void fieldsFormatted() { + SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + assertEquals("2014-06-24 17:33:49", format.format(new Date(1403616829504L))); + } +}