-4

I need to convert big amounts of java.util.Date objects to String, for now I am using

String dateString = new SimpleDateFormat(dateFormat).format(value);

but it requires create a new SimpleDateFormat object each time.

What is the fastest way to parse a String in needed format in a big amounts without creating new SimpleDateFormat objects, for example yyyy-MM-dd pattern from java.util.Date?

I am using java 7. Solutions from java 8 are not acceptable for me

Oleksandr Riznyk
  • 758
  • 1
  • 8
  • 19
  • 9
    Is there a slow way? – Scary Wombat Oct 16 '18 at 08:39
  • 1
    Possible duplicate of [How to parse a date?](https://stackoverflow.com/questions/999172/how-to-parse-a-date) – dube Oct 16 '18 at 08:39
  • 1
    Welcome to SO. Please provide a Minimal, Complete, and Verifiable example. **Show us the code for your latest attempt** and where you got stuck. and explain why the result is not what you expected. Edit your question to include the code, please don't add it in a comment, as it will probably be unreadable. https://stackoverflow.com/help/mcve – Dragonthoughts Oct 16 '18 at 08:40
  • 1
    You should provide what you have tried which is slower – Vithursa Mahendrarajah Oct 16 '18 at 08:41
  • If you are processing large number of `Date` serially, creating a single instance would work (provided they use the same format). If you are trying to do the conversion parallelly, then you would have to create a new instance for each conversion. – Jai Oct 16 '18 at 08:42
  • I wonder where you got your `Date` objects from and whether there could be a shortcut to getting the data before it’s becoming `Date` objects. Usage: *to parse* means to convert *from* a string. The opposite operation, which I believe you are after, is called *formatting*. – Ole V.V. Oct 16 '18 at 09:37
  • There may be some optimization possible depending on the exact data and exact format. If you’ve got a million dates that all fall within 2018, caching of the strings translated to could come into play, for example. – Ole V.V. Oct 16 '18 at 09:39
  • 1
    Most of the *java.time* functionality is back-ported to Java 6 & 7 in [***ThreeTen-Backport***](http://www.threeten.org/threetenbp/). – Basil Bourque Oct 17 '18 at 17:04
  • Your code is *generating* a `String`, not *parsing*. – Basil Bourque Oct 17 '18 at 18:31
  • @Jai If the conversion is done in parallel, the OP just needs to ensure that each thread has its own format object (eg using a `ThreadLocal`). – Mark Rotteveel Oct 18 '18 at 16:31

4 Answers4

2

tl;dr

The back-port of the java.time classes takes under a microsecond to generate a String from a LocalDate in your desired pattern.

String output = myLocalDate.toString() ;  // Takes less than a microsecond.

Using this library, I would not worry about date-time strings being a bottleneck.

ThreeTen-Backport

The modern approach uses the java.time classes that supplanted the terrible old date-time classes such as Date & Calendar. For Java 6 & 7, most of that functionality is back-ported in the ThreeTen-Backport project, using nearly identical API. Add the library and import: import org.threeten.bp.*;

Your example format of YYYY-MM-DD is the default used by the LocalDate class when parsing/generating text.

Example code.

Set up a list of LocalDate objects.

long years = 1000;
LocalDate today = LocalDate.now();
LocalDate lastDate = today.plusYears( years );
int initialCapacity = ( int ) ( ( years + 1 ) * 366 );
List < LocalDate > dates = new ArrayList <>( initialCapacity );
LocalDate localDate = today;
System.out.println( "From: " + today + " to: " + lastDate );
while ( localDate.isBefore( lastDate ) ) {
    dates.add( localDate );
    // Setup next loop.
    localDate = localDate.plusDays( 1 );
}

Run the test.

long start = System.nanoTime();

for ( LocalDate date : dates ) {
    String output = date.toString(); // Generate text in standard ISO 8601 format.
}

long stop = System.nanoTime();
long elapsed = ( stop - start );
long nanosEach = elapsed / dates.size();

System.out.println( "nanosEach: " + nanosEach );

Results: Under a microsecond each

When running on a MacBook Pro (Retina, 15-inch, Late 2013), 2.3 GHz Intel Core i7, 16 GB 1600 MHz DDR3, in IntelliJ 2018.3, using Java 10.0.2 from the OpenJDK-based Zulu JVM from Azul Systems…

When running a batch of 100 years, I get about 650 nanoseconds each. That is about two/thirds of a microsecond.

When running a batch of 1,000 years, I get about 260 nanoseconds each. That is about a quarter of a microsecond.

I doubt processing date strings with this library will prove to be a bottleneck in your app’s performance.

Thread-safety

The java.time classes are designed to be inherently thread-safe, including the use of immutable objects.

You can cache a single DateTimeFormatter object, and use it repeatedly, even across threads.

Your desired format, defined by the ISO 8601 standard, is pre-defined as a constant in both java.time and in the ThreeTen-Backport library: DateTimeFormatter .ISO_LOCAL_DATE.

DateTimeFormatter f = DateTimeFormatter.ofPattern( "uuuu-MM-dd" ) ;  // Or just use the pre-defined constant for that particular pattern, `DateTimeFormatter .ISO_LOCAL_DATE`, also used by default in `LocalDate::toString`.
…
String output = localDate.format( f ) ;

About java.time

The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.

The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.

To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.

You may exchange java.time objects directly with your database. Use a JDBC driver compliant with JDBC 4.2 or later. No need for strings, no need for java.sql.* classes.

Where to obtain the java.time classes?

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
1

To convert many dates into string, you can use same SimpleDateFormat, but do it only in one thread, because SimpleDateFormat is not thread safe. But, as one of possible variant, you can create util class and hold SimpleDateFormat in ThreadLocal variable and use it anywhere.

Slow: SimpleDateFormat is been created multiple times:

for(Date date : hugeDateList)
    String str = new SimpleDateFormat("yyyy-MM-dd").format(date);

Fast: SimpleDateFormat is been created only once and use multiple times

DateFormat df = new SimpleDateFormat("yyyy-MM-dd");

for(Date date : hugeDateList)
    String str = df.format(date);

Faster: SimpleDateFormat is been declared once per thread, date list is been formatted with multiple threads (e.g. 10 threads with 10% of all dates each):

public final class TimeUtils {

    private static final ThreadLocal<DateFormat> THREAD_LOCAL_DATE_FORMAT = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));

    public static String format(Date date) {
        return date != null ? THREAD_LOCAL_DATE_FORMAT.get().format(date) : null;
    }

}

// TODO this is Java8 only for example, in Java7 ther're lines to create and run threads (not related to the question)
hugeDateList.parallelStream().map(TimeUtils::format).collect(Collectors.toList())
Oleg Cherednik
  • 17,377
  • 4
  • 21
  • 35
  • OP said no Java 8 though. – Jai Oct 16 '18 at 09:03
  • @Jai see comment in `TODO` – Oleg Cherednik Oct 16 '18 at 09:03
  • The correct way of implementing parallelism without stream API might be the most complex part in this whole solution though. – Jai Oct 16 '18 at 09:05
  • Create couple thread and use it - is pretty simple and does not relates to the question. Correct answer is that fastest way is to use multiple threads and with only one `SDF` instance for each of them. – Oleg Cherednik Oct 16 '18 at 09:06
  • @Jai Answers on Stack Overflow are not written only for the asker (or it would be better to send an email message), but for everyone with similar questions now and in the future to learn from. So even though the asker is not going to use Java 8 or later just now, information on how to do that is still top relevant in the answer. – Ole V.V. Oct 16 '18 at 09:32
1

parsing&formating are compute-intensive tasks, if your computer has multiple processors, I would suggest to make your task multi-threading.

For exmaple:

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

class MyCallable implements Callable<String> {

    Date date;

    public MyCallable(Date date) {
        this.date = date;
    }

    @Override
    public String call() throws Exception {
        return new SimpleDateFormat(your pattern).format(date);
    }
}

public class Example {

    public static void main(String[] args) {

        // thread pool
        ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

        // your date objects
        List<Date> dates = ...

        List<Future<String>> futureList = new ArrayList<>();

        // let the thread poll run tasks
        for (Date date : dates) {
            MyCallable myCallable = new MyCallable(date);
            futureList.add(executorService.submit(myCallable));
        }

        // collect result
        List<String> results = new ArrayList<>();

        for (Future<String> future : futureList) {
            try {
                results.add(future.get());
            } catch (Exception e) {
                // 
            }
        }
    }
}
xingbin
  • 27,410
  • 9
  • 53
  • 103
  • I was trying to write an answer using `ForkJoinPool`, but I guess it's a little overkill for this. – Jai Oct 16 '18 at 09:02
0

a quicker way would be not to re-create the SimpleDateFormat each time

SimpleDateFormat df = new SimpleDateFormat(dateFormat);  // outside loop or a field 
....
String dateString = df.format(value);
Scary Wombat
  • 44,617
  • 6
  • 35
  • 64
  • Better to add some tests. Make formattor singlton maybe save some space, while it does not gurantee `faster`. – xingbin Oct 16 '18 at 08:46
  • 3
    But keep in mind that the implementation of SimpleDataFormat is NOT thread-safe. Creating the instance once outside a loop is ok, but don't use the same instance in multiple threads. – Felix Oct 16 '18 at 09:03