FastDateFormatTest.java
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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
*
* https://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.apache.commons.lang3.time;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNotSame;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.fail;
import java.text.FieldPosition;
import java.text.Format;
import java.text.ParseException;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLongArray;
import org.apache.commons.lang3.AbstractLangTest;
import org.junit.jupiter.api.Test;
import org.junitpioneer.jupiter.DefaultLocale;
import org.junitpioneer.jupiter.DefaultTimeZone;
/**
* Tests {@link org.apache.commons.lang3.time.FastDateFormat}.
*/
class FastDateFormatTest extends AbstractLangTest {
private static final String ISO_8601_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ssZZ";
private static final int NTHREADS = 10;
private static final int NROUNDS = 10000;
final Locale FINNISH = Locale.forLanguageTag("fi");
final Locale HUNGARIAN = Locale.forLanguageTag("hu");
private AtomicLongArray measureTime(final Format printer, final Format parser) throws InterruptedException {
final ExecutorService pool = Executors.newFixedThreadPool(NTHREADS);
final AtomicInteger failures = new AtomicInteger();
final AtomicLongArray totalElapsed = new AtomicLongArray(2);
try {
for (int i = 0; i < NTHREADS; ++i) {
pool.submit(() -> {
for (int j = 0; j < NROUNDS; ++j) {
try {
final Date date = new Date();
final long t0Millis = System.currentTimeMillis();
final String formattedDate = printer.format(date);
totalElapsed.addAndGet(0, System.currentTimeMillis() - t0Millis);
final long t1Millis = System.currentTimeMillis();
final Object pd = parser.parseObject(formattedDate);
totalElapsed.addAndGet(1, System.currentTimeMillis() - t1Millis);
if (!date.equals(pd)) {
failures.incrementAndGet();
}
} catch (final Exception e) {
failures.incrementAndGet();
e.printStackTrace();
}
}
});
}
} finally {
pool.shutdown();
// depending on the performance of the machine used to run the parsing,
// the tests can run for a while. It should however complete within
// 30 seconds. Might need increase on very slow machines.
if (!pool.awaitTermination(30, TimeUnit.SECONDS)) {
pool.shutdownNow();
fail("did not complete tasks");
}
}
assertEquals(0, failures.get());
return totalElapsed;
}
@DefaultLocale(language = "en", country = "US")
@Test
void test_changeDefault_Locale_DateInstance() {
final FastDateFormat format1 = FastDateFormat.getDateInstance(FastDateFormat.FULL, Locale.GERMANY);
final FastDateFormat format2 = FastDateFormat.getDateInstance(FastDateFormat.FULL);
Locale.setDefault(Locale.GERMANY);
final FastDateFormat format3 = FastDateFormat.getDateInstance(FastDateFormat.FULL);
assertSame(Locale.GERMANY, format1.getLocale());
assertEquals(Locale.US, format2.getLocale());
assertSame(Locale.GERMANY, format3.getLocale());
assertNotSame(format1, format2);
assertNotSame(format2, format3);
}
@DefaultLocale(language = "en", country = "US")
@Test
void test_changeDefault_Locale_DateTimeInstance() {
final FastDateFormat format1 = FastDateFormat.getDateTimeInstance(FastDateFormat.FULL, FastDateFormat.FULL, Locale.GERMANY);
final FastDateFormat format2 = FastDateFormat.getDateTimeInstance(FastDateFormat.FULL, FastDateFormat.FULL);
Locale.setDefault(Locale.GERMANY);
final FastDateFormat format3 = FastDateFormat.getDateTimeInstance(FastDateFormat.FULL, FastDateFormat.FULL);
assertSame(Locale.GERMANY, format1.getLocale());
assertEquals(Locale.US, format2.getLocale());
assertSame(Locale.GERMANY, format3.getLocale());
assertNotSame(format1, format2);
assertNotSame(format2, format3);
}
/*
* Only the cache methods need to be tested here.
* The print methods are tested by {@link FastDateFormat_PrinterTest}
* and the parse methods are tested by {@link FastDateFormat_ParserTest}
*/
@Test
void test_getInstance() {
final FastDateFormat format1 = FastDateFormat.getInstance();
final FastDateFormat format2 = FastDateFormat.getInstance();
assertSame(format1, format2);
}
@Test
void test_getInstance_String() {
final FastDateFormat format1 = FastDateFormat.getInstance("MM/DD/yyyy");
final FastDateFormat format2 = FastDateFormat.getInstance("MM-DD-yyyy");
final FastDateFormat format3 = FastDateFormat.getInstance("MM-DD-yyyy");
assertNotSame(format1, format2);
assertSame(format2, format3);
assertEquals("MM/DD/yyyy", format1.getPattern());
assertEquals(TimeZone.getDefault(), format1.getTimeZone());
assertEquals(TimeZone.getDefault(), format2.getTimeZone());
}
@DefaultLocale(language = "en", country = "US")
@Test
void test_getInstance_String_Locale() {
final FastDateFormat format1 = FastDateFormat.getInstance("MM/DD/yyyy", Locale.GERMANY);
final FastDateFormat format2 = FastDateFormat.getInstance("MM/DD/yyyy");
final FastDateFormat format3 = FastDateFormat.getInstance("MM/DD/yyyy", Locale.GERMANY);
assertNotSame(format1, format2);
assertSame(format1, format3);
assertEquals(Locale.GERMANY, format1.getLocale());
}
@DefaultLocale(language = "en", country = "US")
@DefaultTimeZone("America/New_York")
@Test
void test_getInstance_String_TimeZone() {
final FastDateFormat format1 = FastDateFormat.getInstance("MM/DD/yyyy",
TimeZone.getTimeZone("Atlantic/Reykjavik"));
final FastDateFormat format2 = FastDateFormat.getInstance("MM/DD/yyyy");
final FastDateFormat format3 = FastDateFormat.getInstance("MM/DD/yyyy", TimeZone.getDefault());
final FastDateFormat format4 = FastDateFormat.getInstance("MM/DD/yyyy", TimeZone.getDefault());
final FastDateFormat format5 = FastDateFormat.getInstance("MM-DD-yyyy", TimeZone.getDefault());
final FastDateFormat format6 = FastDateFormat.getInstance("MM-DD-yyyy");
assertNotSame(format1, format2);
assertEquals(TimeZone.getTimeZone("Atlantic/Reykjavik"), format1.getTimeZone());
assertEquals(TimeZone.getDefault(), format2.getTimeZone());
assertSame(format3, format4);
assertNotSame(format3, format5);
assertNotSame(format4, format6);
}
@DefaultLocale(language = "en", country = "US")
@DefaultTimeZone("America/New_York")
@Test
void test_getInstance_String_TimeZone_Locale() {
final FastDateFormat format1 = FastDateFormat.getInstance("MM/DD/yyyy",
TimeZone.getTimeZone("Atlantic/Reykjavik"), Locale.GERMANY);
final FastDateFormat format2 = FastDateFormat.getInstance("MM/DD/yyyy", Locale.GERMANY);
final FastDateFormat format3 = FastDateFormat.getInstance("MM/DD/yyyy",
TimeZone.getDefault(), Locale.GERMANY);
assertNotSame(format1, format2);
assertEquals(TimeZone.getTimeZone("Atlantic/Reykjavik"), format1.getTimeZone());
assertEquals(TimeZone.getDefault(), format2.getTimeZone());
assertEquals(TimeZone.getDefault(), format3.getTimeZone());
assertEquals(Locale.GERMANY, format1.getLocale());
assertEquals(Locale.GERMANY, format2.getLocale());
assertEquals(Locale.GERMANY, format3.getLocale());
}
@Test
void testCheckDefaults() {
final FastDateFormat format = FastDateFormat.getInstance();
final FastDateFormat medium = FastDateFormat.getDateTimeInstance(FastDateFormat.SHORT, FastDateFormat.SHORT);
assertEquals(medium, format);
final SimpleDateFormat sdf = new SimpleDateFormat();
assertEquals(sdf.toPattern(), format.getPattern());
assertEquals(Locale.getDefault(), format.getLocale());
assertEquals(TimeZone.getDefault(), format.getTimeZone());
}
@Test
void testCheckDifferingStyles() {
final FastDateFormat shortShort = FastDateFormat.getDateTimeInstance(FastDateFormat.SHORT, FastDateFormat.SHORT, Locale.US);
final FastDateFormat shortLong = FastDateFormat.getDateTimeInstance(FastDateFormat.SHORT, FastDateFormat.LONG, Locale.US);
final FastDateFormat longShort = FastDateFormat.getDateTimeInstance(FastDateFormat.LONG, FastDateFormat.SHORT, Locale.US);
final FastDateFormat longLong = FastDateFormat.getDateTimeInstance(FastDateFormat.LONG, FastDateFormat.LONG, Locale.US);
assertNotEquals(shortShort, shortLong);
assertNotEquals(shortShort, longShort);
assertNotEquals(shortShort, longLong);
assertNotEquals(shortLong, longShort);
assertNotEquals(shortLong, longLong);
assertNotEquals(longShort, longLong);
}
@Test
void testDateDefaults() {
assertEquals(FastDateFormat.getDateInstance(FastDateFormat.LONG, Locale.CANADA),
FastDateFormat.getDateInstance(FastDateFormat.LONG, TimeZone.getDefault(), Locale.CANADA));
assertEquals(FastDateFormat.getDateInstance(FastDateFormat.LONG, TimeZone.getTimeZone("America/New_York")),
FastDateFormat.getDateInstance(FastDateFormat.LONG, TimeZone.getTimeZone("America/New_York"), Locale.getDefault()));
assertEquals(FastDateFormat.getDateInstance(FastDateFormat.LONG),
FastDateFormat.getDateInstance(FastDateFormat.LONG, TimeZone.getDefault(), Locale.getDefault()));
}
@Test
void testLang1152() {
final TimeZone utc = FastTimeZone.getGmtTimeZone();
final Date date = new Date(Long.MAX_VALUE);
String dateAsString = FastDateFormat.getInstance("yyyy-MM-dd", utc, Locale.US).format(date);
assertEquals("292278994-08-17", dateAsString);
dateAsString = FastDateFormat.getInstance("dd/MM/yyyy", utc, Locale.US).format(date);
assertEquals("17/08/292278994", dateAsString);
}
@Test
void testLang1267() {
FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");
}
@Test
void testLang1641() {
assertSame(FastDateFormat.getInstance(ISO_8601_DATE_FORMAT), FastDateFormat.getInstance(ISO_8601_DATE_FORMAT));
// commons-lang's GMT TimeZone
assertSame(FastDateFormat.getInstance(ISO_8601_DATE_FORMAT, FastTimeZone.getGmtTimeZone()),
FastDateFormat.getInstance(ISO_8601_DATE_FORMAT, FastTimeZone.getGmtTimeZone()));
// default TimeZone
assertSame(FastDateFormat.getInstance(ISO_8601_DATE_FORMAT, TimeZone.getDefault()),
FastDateFormat.getInstance(ISO_8601_DATE_FORMAT, TimeZone.getDefault()));
// TimeZones that are identical in every way except ID
assertNotSame(FastDateFormat.getInstance(ISO_8601_DATE_FORMAT, TimeZone.getTimeZone("Australia/Broken_Hill")),
FastDateFormat.getInstance(ISO_8601_DATE_FORMAT, TimeZone.getTimeZone("Australia/Yancowinna")));
}
/**
* According to LANG-954 (https://issues.apache.org/jira/browse/LANG-954) this is broken in Android 2.1.
*/
@Test
void testLang954() {
final String pattern = "yyyy-MM-dd'T'";
FastDateFormat.getInstance(pattern);
}
/**
* Tests [LANG-1767] FastDateFormat.parse can not recognize "CEST" Timezone.
*
* @throws ParseException Throws on test failure.
*/
@Test
void testParseCentralEuropeanSummerTime() throws ParseException {
assertNotNull(FastDateFormat.getInstance("dd.MM.yyyy HH:mm:ss", Locale.GERMANY).parse("26.10.2014 02:00:00"));
assertNotNull(FastDateFormat.getInstance("dd.MM.yyyy HH:mm:ss z", Locale.US).parse("26.10.2014 02:00:00 CEST"));
assertNotNull(FastDateFormat.getInstance("dd.MM.yyyy HH:mm:ss z", Locale.GERMANY).parse("26.10.2014 02:00:00 MESZ"));
}
@Test
void testParseSync() throws InterruptedException {
final String pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS";
final SimpleDateFormat inner = new SimpleDateFormat(pattern);
final Format sdf = new Format() {
private static final long serialVersionUID = 1L;
@Override
public StringBuffer format(final Object obj, final StringBuffer toAppendTo, final FieldPosition fieldPosition) {
synchronized (this) {
return inner.format(obj, toAppendTo, fieldPosition);
}
}
@Override
public Object parseObject(final String source, final ParsePosition pos) {
synchronized (this) {
return inner.parseObject(source, pos);
}
}
};
final AtomicLongArray sdfTime = measureTime(sdf, sdf);
final Format fdf = FastDateFormat.getInstance(pattern);
final AtomicLongArray fdfTime = measureTime(fdf, fdf);
// System.out.println(">>FastDateFormatTest: FastDatePrinter:"+fdfTime.get(0)+" SimpleDateFormat:"+sdfTime.get(0));
// System.out.println(">>FastDateFormatTest: FastDateParser:"+fdfTime.get(1)+" SimpleDateFormat:"+sdfTime.get(1));
}
@Test
void testStandaloneLongMonthForm() {
final TimeZone utc = FastTimeZone.getGmtTimeZone();
final Instant testInstant = LocalDate.of(1970, 9, 15).atStartOfDay(ZoneId.of("UTC")).toInstant();
final Date date = Date.from(testInstant);
String dateAsString = FastDateFormat.getInstance("yyyy-LLLL-dd", utc, Locale.GERMAN).format(date);
assertEquals("1970-September-15", dateAsString);
dateAsString = FastDateFormat.getInstance("yyyy-LLLL-dd", utc, FINNISH).format(date);
assertEquals("1970-syyskuu-15", dateAsString);
dateAsString = FastDateFormat.getInstance("yyyy-LLLL-dd", utc, HUNGARIAN).format(date);
assertEquals("1970-szeptember-15", dateAsString);
}
@Test
void testStandaloneShortMonthForm() {
final TimeZone utc = FastTimeZone.getGmtTimeZone();
final Instant testInstant = LocalDate.of(1970, 9, 15).atStartOfDay(ZoneId.of("UTC")).toInstant();
final Date date = Date.from(testInstant);
String dateAsString = FastDateFormat.getInstance("yyyy-LLL-dd", utc, Locale.GERMAN).format(date);
assertEquals("1970-Sep-15", dateAsString);
dateAsString = FastDateFormat.getInstance("yyyy-LLL-dd", utc, FINNISH).format(date);
assertEquals("1970-syys-15", dateAsString);
dateAsString = FastDateFormat.getInstance("yyyy-LLL-dd", utc, HUNGARIAN).format(date);
assertEquals("1970-szept.-15", dateAsString);
}
@Test
void testTimeDateDefaults() {
assertEquals(FastDateFormat.getDateTimeInstance(FastDateFormat.LONG, FastDateFormat.MEDIUM, Locale.CANADA),
FastDateFormat.getDateTimeInstance(FastDateFormat.LONG, FastDateFormat.MEDIUM, TimeZone.getDefault(), Locale.CANADA));
assertEquals(FastDateFormat.getDateTimeInstance(FastDateFormat.LONG, FastDateFormat.MEDIUM, TimeZone.getTimeZone("America/New_York")),
FastDateFormat.getDateTimeInstance(FastDateFormat.LONG, FastDateFormat.MEDIUM, TimeZone.getTimeZone("America/New_York"), Locale.getDefault()));
assertEquals(FastDateFormat.getDateTimeInstance(FastDateFormat.LONG, FastDateFormat.MEDIUM),
FastDateFormat.getDateTimeInstance(FastDateFormat.LONG, FastDateFormat.MEDIUM, TimeZone.getDefault(), Locale.getDefault()));
}
@Test
void testTimeDefaults() {
assertEquals(FastDateFormat.getTimeInstance(FastDateFormat.LONG, Locale.CANADA),
FastDateFormat.getTimeInstance(FastDateFormat.LONG, TimeZone.getDefault(), Locale.CANADA));
assertEquals(FastDateFormat.getTimeInstance(FastDateFormat.LONG, TimeZone.getTimeZone("America/New_York")),
FastDateFormat.getTimeInstance(FastDateFormat.LONG, TimeZone.getTimeZone("America/New_York"), Locale.getDefault()));
assertEquals(FastDateFormat.getTimeInstance(FastDateFormat.LONG),
FastDateFormat.getTimeInstance(FastDateFormat.LONG, TimeZone.getDefault(), Locale.getDefault()));
}
}