11
\$\begingroup\$

I am facing a issue with an API to handle dates in Java. I am not a very experienced programmer and my English is very bad.

Problem

I'm working in a large project with many subsystems, and I want to make a Date Utility library to manage all dates in the system.

Requirements

Platform

  • The front end is a JSF page with RichFaces
  • The application generate reports with JasperReports
  • The back end is a Spring 3.1 Application
  • Is desirable to support Calendar and XMLGregorianCalendar (for Jaxb2 Serialization)

Business Logic

  • The system must support six types of dates:
    • Short date format: dd-MM-yy
    • Date format: dd-MM-yyyy
    • Time format (only hours and minutes): HH:mm
    • Time and date: dd-MM-yyyy HH:mm
    • Time and short date: dd-MM-yy HH:mm
  • The consistency is the most important thing in the design

The long format is for details, and the short is for grids and places with little space.

API

// Format using dd-MM-yy
public String asShortDate(@Nullable Date date) ;

// Parse using dd-MM-yyy
public Date parseShortDate(@Nullable String string) throws ParseException;

// Format using dd-MM-yyyy
public String asDate(@Nullable Date date);

// Parse using dd-MM-yyyy
public Date parseDate(@Nullable String string) throws ParseException;

// Format using HH:mm
public String asTime(@Nullable Date date);

// Parse using HH:mm
public Date parseTime(@Nullable String string) throws ParseException;

// Format using dd-MM-yyyy HH:mm
public String asDateTime(@Nullable Date date);

// Parse using dd-MM-yyyy HH:mm
public Date parseDateTime(@Nullable String string) throws ParseException;

// Format using dd-MM-yy HH:mm
public String asShortDateTime(@Nullable Date date) ;

// Parse using dd-MM-yy HH:mm
public Date parseShortDateTime(@Nullable String string) throws ParseException;

Parser and Formatter

protected synchronized String dateToString(Date date, String format) {

    if (date == null) {
        return EMPTY_STRING;
    }
    return getFormat(format).format(date);
}

/**
 * @param string
 * @return
 * @throws ParseException
 */
protected synchronized Date stringToDate(String string, String format)
        throws ParseException {

    if (string == null || EMPTY_STRING.equals(string)) {
        return null;
    }
    SimpleDateFormat sdf = getFormat(format);
    ParsePosition psp = new ParsePosition(0);
    if (string.length() != format.length()) {
        throw new ParseException("Imposible parsear", 0);
    }
    Date toRet = sdf.parse(string, psp);
    if (psp.getIndex() != string.length()) {
        throw new ParseException("Imposible parsear", psp.getIndex());
    }
    return toRet;
}

/**
 * Provee un formato
 * 
 * @param format
 * @return
 */
private SimpleDateFormat getFormat(String format) {

    if (!initialized) {
        init();
    }
    if (!formats.containsKey(format)) {
        throw new IllegalArgumentException("Unknow format " + format);
    }
    return formats.get(format);
}

Other

private synchronized void init() {

    if (initialized) {
        return;
    }
    initialized = true;
    formats = new HashMap<String, SimpleDateFormat>(5);
    formats.put(DATE_FORMAT, new SimpleDateFormat(DATE_FORMAT));
    formats.put(DATE_SHORT_FORMAT, new SimpleDateFormat(DATE_SHORT_FORMAT));
    formats.put(TIME_FORMAT, new SimpleDateFormat(TIME_FORMAT));
    formats.put(DATETIME_FORMAT, new SimpleDateFormat(DATETIME_FORMAT));
    formats.put(DATETIME_SHORT_FORMAT, new SimpleDateFormat(
            DATETIME_SHORT_FORMAT));
    for (SimpleDateFormat sdf : formats.values()) {
        sdf.setLenient(false);
    }
}

Considerations

  • I made the methods stringToDate, dateToString and init as synchronized because SimpleDateFormat is not thread-safe, and I only want one initialization.
  • I made the DateFormat not lenient because I don't want to handle wrong format dates
  • The methods names are for enhance readability (as* always return a String, and parse* always return a Date).

Further develop

  • A JSF Date converter to manage automatically all dates.
  • A JSF Phase listener to format all dates.
  • A @Annotation to differentiate between different kinds of types

My Questions

  • Is this approach right and maintainable?
  • My considerations are right?
  • How I can improve this API?
  • Is correct to be restrictive about the lenient property, or is a waste of time?

Source

Edit

Observation: this question was answered in stack overflow

\$\endgroup\$

2 Answers 2

8
\$\begingroup\$

Dates and Date handling in Java have been a problem since the beginning (check out all the deprecated methods in version 1.1). You appear to be running in to the same problems that may Java developers have encountered, and then there are a whole lot of issues that you have not yet encountered.

Unlike other questions in CodeReview, I'm going to deviate from the norm, and tell you to give up.... A lot of people have taken a kick at this can... people smarter than you, and me, and the bottom line is that, even after three attempts, the base Java implementation for Date and Date-like data is still not right.

On the other hand, JodaTime has somehow managed to be a library that is well maintained, accurate, usable, and just 'gets it'. It is licensed under the Apache2 license, which means there is effectively no restriction on how you can use it, and with few restrictions/requirements when modifying it (not that you would want to).

At this point in the Java ecosystem, there is absolutely no point in building your own Date library.... it leads to weeping and gnashing of teeth.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ JodaTime was one of my options, but, at time when I ask this, was impossible to add new Dependencies (for policy decisions). I end up with my specification, and works pretty well, my main problem now if how to make the developers use it!. Cheers. \$\endgroup\$ Commented Feb 4, 2014 at 10:36
2
\$\begingroup\$

Writing your own code to parse dates (as in performing calculation and validation) would be reinventing the wheel, and therefore a bad idea. However, making some convenience routines to help enforce some consistency in your input and output formats, as you have done, is a good idea.


Forcing all of your parsing and formatting routines to be synchronized is an unnecessary burden. To start with, dateToString() and stringToDate() don't need to be synchronized. As it is currently written, getFormat() does need to be synchronized, because formats might change while you call formats.containsKey() or formats.get(). However, even that synchronization could be avoided, since you would only mutate formats during initialization. If you perform the initialization in a static initializer block, then the initialization will happen exactly once at class-loading time, in a thread-safe manner (JLS 12.4.2).

In summary, initialize formats like this:

static {
    formats = new HashMap<String, SimpleDateFormat>(5);
    formats.put(DATE_FORMAT, new SimpleDateFormat(DATE_FORMAT));
    formats.put(DATE_SHORT_FORMAT, new SimpleDateFormat(DATE_SHORT_FORMAT));
    formats.put(TIME_FORMAT, new SimpleDateFormat(TIME_FORMAT));
    formats.put(DATETIME_FORMAT, new SimpleDateFormat(DATETIME_FORMAT));
    formats.put(DATETIME_SHORT_FORMAT, new SimpleDateFormat(
            DATETIME_SHORT_FORMAT));
    for (SimpleDateFormat sdf : formats.values()) {
        sdf.setLenient(false);
    }
}

… and drop all use of synchronized in your code.


In the Java date-time API, a Date isn't really what most people think of as a "date" — it's actually a count of milliseconds since the Java epoch (1970-01-01 00:00:00 UTC), analogous to the time_t type in C. Therefore, converting a String such as "2014-02-04" into a Date isn't just an act of parsing (as in textual analysis) — it also involves interpreting that timestamp in the context of a particular time zone (the time zone of your server process, by default). Depending on whether your web application's users are geographically concentrated, that may or may not be appropriate. Joda-Time supports a timezone-neutral date/time representation; the Java API doesn't.


When you throw a ParseException, it would be better to include the malformed date in the exception message. "Imposible parsear" is redundant with ParseException and therefore uninformative.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.