diff --git a/CodenameOne/src/com/codename1/l10n/SimpleDateFormat.java b/CodenameOne/src/com/codename1/l10n/SimpleDateFormat.java index 86979fec3c..ecb178d562 100644 --- a/CodenameOne/src/com/codename1/l10n/SimpleDateFormat.java +++ b/CodenameOne/src/com/codename1/l10n/SimpleDateFormat.java @@ -118,6 +118,18 @@ public class SimpleDateFormat extends DateFormat { * Pattern character for RFC 822-style timezone. */ private static final char TIMEZONE822_LETTER = 'Z'; + /** + * Pattern character for ISO 8601 timezone. + */ + private static final char ISO_TIMEZONE_LETTER = 'X'; + /** + * Pattern character for week year. + */ + private static final char WEEK_YEAR_LETTER = 'Y'; + /** + * Pattern character for day number of week. + */ + private static final char DAY_NUMBER_OF_WEEK_LETTER = 'u'; /** * Internally used character for literal text. */ @@ -141,7 +153,7 @@ public class SimpleDateFormat extends DateFormat { /** * Pattern characters recognized by this implementation (same as JDK 1.6). */ - private static final String PATTERN_LETTERS = "adDEFGHhKkMmsSwWyzZ"; + private static final String PATTERN_LETTERS = "adDEFGHhKkMmsSwWyzZXYu"; /** * TimeZone ID for Greenwich Mean Time */ @@ -371,7 +383,24 @@ String format(Date source, StringBuilder toAppendTo) { toAppendTo.append(leftPad(v / 60, 2)); toAppendTo.append(leftPad(v % 60, 2)); break; + case ISO_TIMEZONE_LETTER: + v = getOffsetInMinutes(calendar, calendar.getTimeZone()); + if (v < 0) { + toAppendTo.append(SIGN_NEGATIVE); + v = -v; + } else { + toAppendTo.append(SIGN_POSITIVE); + } + toAppendTo.append(leftPad(v / 60, 2)); + if (len >= 2) { + if (len == 3) { + toAppendTo.append(':'); + } + toAppendTo.append(leftPad(v % 60, 2)); + } + break; case YEAR_LETTER: + case WEEK_YEAR_LETTER: v = calendar.get(Calendar.YEAR); if (len == 2) { v %= 100; @@ -441,6 +470,10 @@ String format(Date source, StringBuilder toAppendTo) { v = calendar.get(DAY_OF_WEEK_IN_MONTH); toAppendTo.append(leftPad(v, len)); break; + case DAY_NUMBER_OF_WEEK_LETTER: + v = ((calendar.get(Calendar.DAY_OF_WEEK) + 5) % 7) + 1; + toAppendTo.append(leftPad(v, len)); + break; } } return toAppendTo.toString(); @@ -515,6 +548,7 @@ public Date parse(String source) throws ParseException { break; case TIMEZONE_LETTER: case TIMEZONE822_LETTER: + case ISO_TIMEZONE_LETTER: s = readTimeZone(source, startIndex); TimeZoneResult res = new TimeZoneResult(); if (s == null || (v = parseTimeZone(s, startIndex, res)) == -1) { @@ -525,6 +559,7 @@ public Date parse(String source) throws ParseException { tzMinutes = ((tzMinutes == -1) ? 0 : tzMinutes) + v; break; case YEAR_LETTER: + case WEEK_YEAR_LETTER: s = readNumber(source, startIndex, token, adjacent); calendar.set(Calendar.YEAR, parseYear(s, token, startIndex)); break; @@ -572,6 +607,14 @@ public Date parse(String source) throws ParseException { calendar.set(DAY_OF_WEEK_IN_MONTH, parseNumber(s, startIndex, "day of week in month", -5, 5)); break; + case DAY_NUMBER_OF_WEEK_LETTER: + s = readNumber(source, startIndex, token, adjacent); + v = parseNumber(s, startIndex, "day number of week", 1, 7); + // Map 1(Mon)-7(Sun) to Calendar 2(Mon)..1(Sun) + // Mon=1 -> 2. Tue=2 -> 3. ... Sat=6 -> 7. Sun=7 -> 1. + // v % 7 + 1 + calendar.set(Calendar.DAY_OF_WEEK, (v % 7) + 1); + break; } if (s != null) { startIndex += s.length(); @@ -1186,14 +1229,27 @@ List parseDatePattern(String pattern) { char ch = pattern.charAt(i); // Handle literal text enclosed in quotes if (ch == EXPLICIT_LITERAL) { - int n = pattern.indexOf(EXPLICIT_LITERAL, i + 1); - if (n != -1) { - if (tmp != null) { - tokens.add(tmp.charAt(0) + tmp); - tmp = null; + if (tmp != null) { + tokens.add(tmp.charAt(0) + tmp); + tmp = null; + } + StringBuilder sb = new StringBuilder(); + int n = i + 1; + while (n < plen) { + char c = pattern.charAt(n); + if (c == EXPLICIT_LITERAL) { + if (n + 1 < plen && pattern.charAt(n + 1) == EXPLICIT_LITERAL) { + sb.append(EXPLICIT_LITERAL); + n += 2; + continue; + } else { + break; + } } - tokens.add(LITERAL_LETTER + pattern.substring(i + 1, n)); + sb.append(c); + n++; } + tokens.add(LITERAL_LETTER + sb.toString()); i = n; continue; } @@ -1208,7 +1264,7 @@ List parseDatePattern(String pattern) { int n; for (n = i; n < plen; n++) { ch = pattern.charAt(n); - if (PATTERN_LETTERS.indexOf(ch) != -1) { + if (PATTERN_LETTERS.indexOf(ch) != -1 || ch == EXPLICIT_LITERAL) { break; } if (isAlpha(ch)) { diff --git a/maven/core-unittests/src/test/java/com/codename1/l10n/SimpleDateFormatTest.java b/maven/core-unittests/src/test/java/com/codename1/l10n/SimpleDateFormatTest.java index 77a9d977b1..c72c820ee6 100644 --- a/maven/core-unittests/src/test/java/com/codename1/l10n/SimpleDateFormatTest.java +++ b/maven/core-unittests/src/test/java/com/codename1/l10n/SimpleDateFormatTest.java @@ -133,6 +133,58 @@ void cloneCopiesPatternAndSymbols() { assertSame(symbols, clone.getDateFormatSymbols()); } + @Test + void testQuotedStringLiteral() { + // "formats like yyyy.MM.dd G 'at' HH:mm:ss z yield illegal argument exception on the t char." + assertDoesNotThrow(() -> { + new SimpleDateFormat("yyyy.MM.dd G 'at' HH:mm:ss z"); + }, "Should not throw exception for quoted literal 'at'"); + } + + @Test + void testEscapedSingleQuoteFormat() { + // "Also formats like: hh 'o''clock' yield exception for the c char." + SimpleDateFormat sdf = new SimpleDateFormat("hh 'o''clock'"); + Calendar cal = Calendar.getInstance(); + cal.set(Calendar.HOUR, 5); + String result = sdf.format(cal.getTime()); + // Expected: 05 o'clock + assertTrue(result.contains("o'clock"), "Should contain single quote: " + result); + } + + @Test + void testXXXFormat() { + // "yyyy-MM-dd'T'HH:mm:ss.SSSXXX (exception for X char, recent API level prerequisite)" + assertDoesNotThrow(() -> { + new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX"); + }, "Should not throw exception for XXX format"); + + SimpleDateFormat sdf = new SimpleDateFormat("XXX"); + // We cannot reliably test exact timezone string without controlling default timezone, + // but we can check it doesn't crash and produces something reasonable. + String result = sdf.format(new Date()); + assertNotNull(result); + assertFalse(result.isEmpty()); + } + + @Test + void testYYYYAnduFormat() { + // "YYYY-'W'ww-u (exception for Y char)" + assertDoesNotThrow(() -> { + new SimpleDateFormat("YYYY-'W'ww-u"); + }, "Should not throw exception for YYYY and u format"); + + SimpleDateFormat sdf = new SimpleDateFormat("u"); + Calendar cal = Calendar.getInstance(); + cal.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY); + String result = sdf.format(cal.getTime()); + assertEquals("1", result, "Monday should be 1"); + + cal.set(Calendar.DAY_OF_WEEK, Calendar.SUNDAY); + result = sdf.format(cal.getTime()); + assertEquals("7", result, "Sunday should be 7"); + } + private static class StubL10NManager extends L10NManager { StubL10NManager() { super("en", "US");