Search code examples
javaandroidjsondateapache-commons

JSON UTC date string to local date string using apache common


I came from another background to Java. This question might be look silly but I'm unable to solve it with my current skills.

My server returns UTC date as json string. I'm using Gson with custom Type Adapter and converted it in required date format (plus local timezone) using SimpleDateFormat. But as SimpleDateFormat is not thread safe so I'm trying to use Apache common lang library to obtain the same in which I'm failing.

    //private final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    private static final FastDateFormat fdf = FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss", TimeZone.getTimeZone("UTC"));

    @Override
    public Date deserialize(JsonElement element, Type arg1, JsonDeserializationContext arg2) throws JsonParseException {

        try {
            return DateUtils.parseDate(element.getAsString(), "yyyy-MM-dd HH:mm:ss");
        } catch (ParseException e) {
            return null;
        }
        /*
        sdf.setTimeZone(TimeZone.getTimeZone("UTC"));

        try {
            return sdf.parse(element.getAsString());
        } catch (ParseException e) {
            return null;
        }
        */
    }

I was expecting something like fdf.parse() to convert UTC date string to local date and don't know what should I use.

And in my model:

public class UserData {
    ...
    private Date dt;

    private final SimpleDateFormat sdf = new SimpleDateFormat("MMM dd, yyyy hh:mm a");

    public String getTime() {
        //return dt == null ? "" : sdf.format(dt);
        return DateFormatUtils.format(dt, "MMM dd, yyyy hh:mm a");
    }
}

My requirement is to convert UTC date string to local date string using Apache common (Thread safe). Also if there is optimize code to perform the same, that would be more appreciated.

Also I don't need date as Date datatype so simply it can be turned to String/ removing custom type adapter if date can be displayed as desired format ("MMM dd, yyyy hh:mm a").


Solution

  • Using the class FastDateFormat in the library Apache Commons v3.3.2 works for me this way:

    FastDateFormat parser =
        FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss", TimeZone.getTimeZone("UTC"));
    Date d = parser.parse("2015-11-17 19:29:39");
    System.out.println(d); // in my time zone Europe/Berlin: Tue Nov 17 20:29:39 CET 2015
    
    FastDateFormat printer =
        FastDateFormat.getInstance(
            "MMM dd, yyyy hh:mm a",
            TimeZone.getDefault(),
            Locale.ENGLISH
        );
    System.out.println(printer.format(d)); // Nov 17, 2015 08:29 PM
    

    Some notes:

    Yes, Apache says it is a thread-safe version of SimpleDateFormat, meaning you can use it as an ad-hoc-replacement (similar API) and store the format object in a static final constant. So better performance can be expected in a multi-thread environment. But please don't have too high expectations about performance. It is only quicker compared with old SimpleDateFormat.

    According to my own tests other more modern libraries seem to be quicker and are also thread-safe: JSR-310 (java.time.format-package in Java-8), ThreetenABP (backported to Android), Joda-Time-Android and Time4A (my own library, obviously the quickest - roughly double speed). So I think it is worth to consider these other library alternatives if you care about performance, too.

    Update:

    I have now downloaded and tested the version v3.4 successfully, so I cannot reproduce here your comment. I would also not expect the removal of such an important method in a non-major release if at all. Maybe you just believe that you have v3.4 but another older version is active.

    Examples using other libraries:

    Threeten-ABP (adaptation of backport of JSR-310 to Android)

    roughly the same parsing speed as Apache Commons Lang

    static final DateTimeFormatter PARSER =
        DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withZone(
            ZoneOffset.UTC
        );
    static final DateTimeFormatter PRINTER =
        DateTimeFormatter.ofPattern("MMM dd, yyyy hh:mm a", Locale.ENGLISH)
        .withZone(ZoneId.systemDefault());
    
    public static String toLocaleZone(String utc) {
      ZonedDateTime zdt = ZonedDateTime.parse("2015-11-17 19:29:39", PARSER);      
      return PRINTER.format(zdt);
    }
    

    Joda-Time-Android (adaptation of Joda-Time for Android)

    a little bit quicker parsing than ThreetenABP or Apache Commons

    static final DateTimeFormatter PARSER =
        DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss").withZoneUTC();
    static final DateTimeFormatter PRINTER =
        DateTimeFormat.forPattern("MMM dd, yyyy hh:mm a").withLocale(Locale.ENGLISH).withZone(
            DateTimeZone.getDefault()
        );
    
    public static String toLocaleZone(String utc) {
      DateTime dt = PARSER.parseDateTime("2015-11-17 19:29:39");
      return PRINTER.print(dt);
    }
    

    Time4A (adaptation of Time4J to Android)

    the quickest approach (roughly double speed of parser)

    static final ChronoFormatter<Moment> PARSER =
        ChronoFormatter.ofMomentPattern(
            "yyyy-MM-dd HH:mm:ss",
            PatternType.CLDR,
            Locale.ROOT,
            ZonalOffset.UTC
        );
    static final ChronoFormatter<PlainTimestamp> PRINTER =
        ChronoFormatter.ofTimestampPattern(
            "MMM dd, yyyy hh:mm a",
            PatternType.CLDR,
            Locale.ENGLISH
        );
    
    public static String toLocaleZone(String utc) throws ParseException {
      Moment m = PARSER.parse(utc); // "2015-11-17 19:29:39"
      return PRINTER.format(m.toLocalTimestamp()); // Nov 17, 2015 08:29 pm
    }