Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is SimpleDateFormat changing the date?

Given the following code:

[...]

    public void testFormatDateString() throws ParseException {

        String dateString = new java.util.Date().toString();

        System.out.println(dateString);

        SimpleDateFormat format = new SimpleDateFormat("EEE MMM dd HH:mm:ss z YYYY", Locale.ENGLISH);

        Date date = format.parse(dateString);

        System.out.println(date.toString());
    }

[...]

Before: Sat Aug 19 18:26:11 BST 2017

After: Sat Jan 07 17:26:11 GMT 2017

Why is the date changed?

like image 383
crmepham Avatar asked Jan 03 '23 14:01

crmepham


2 Answers

The upper case Y is for "week year", which has 364 or 371 days instead of the usual 365 or 366. With lower case y (which is used by Date#toString) everything works as expected:

public void testFormatDateString() throws ParseException {

    String dateString = new java.util.Date().toString();

    System.out.println(dateString);

    // Force to Locale.US as this is hardcoded in Date#toString
    SimpleDateFormat format = new SimpleDateFormat(
            "EEE MMM dd HH:mm:ss z yyyy", Locale.US);

    Date date = format.parse(dateString);

    System.out.println(date.toString());
}

Output:

Sat Aug 19 17:50:39 GMT 2017
Sat Aug 19 17:50:39 GMT 2017

See on ideone.com

As mentioned in the comments, make sure to include Locale.US when parsing the dateString, as that is hardcoded in Date#toString. See this question for details.

like image 105
Marvin Avatar answered Jan 14 '23 13:01

Marvin


The first of all I would say that I completely agree with Marvins answer but I hope someone will be interested in more technical details.

Following java doc for SimpleDateFormat you can't find example with YYYY. But it works and validation is passed. Checking deeper:

public class SimpleDateFormat extends DateFormat {
//....
/**
 * Returns the compiled form of the given pattern. The syntax of
 * the compiled pattern is:
 * <blockquote>
 * CompiledPattern:
 *     EntryList
 * EntryList:
 *     Entry
 *     EntryList Entry
 * Entry:
 *     TagField
 *     TagField data
 * TagField:
 *     Tag Length
 *     TaggedData
 * Tag:
 *     pattern_char_index
 *     TAG_QUOTE_CHARS
 * Length:
 *     short_length
 *     long_length
 * TaggedData:
 *     TAG_QUOTE_ASCII_CHAR ascii_char
 *
 * </blockquote>
 * ....
 *
 * @exception NullPointerException if the given pattern is null
 * @exception IllegalArgumentException if the given pattern is invalid
 */
    private char[] compile(String pattern) {
        ...
        if ((tag = DateFormatSymbols.patternChars.indexOf(c)) == -1) {
            throw new IllegalArgumentException("Illegal pattern character " +
                                               "'" + c + "'");
        }
        ...
    }
    ...
}

Checking allowed pattern symbols (that used in validation condition) DateFormatSymbols.patternChars:

public class DateFormatSymbols implements Serializable, Cloneable {
    ...
    static final String  patternChars = "GyMdkHmsSEDFwWahKzZYuXL";
    ...
}

Y is valid pattern element, what's it means (lets check constants in same DateFormatSymbols class)?

static final int PATTERN_ERA                  =  0; // G
static final int PATTERN_YEAR                 =  1; // y
static final int PATTERN_MONTH                =  2; // M
static final int PATTERN_DAY_OF_MONTH         =  3; // d
static final int PATTERN_HOUR_OF_DAY1         =  4; // k
static final int PATTERN_HOUR_OF_DAY0         =  5; // H
static final int PATTERN_MINUTE               =  6; // m
static final int PATTERN_SECOND               =  7; // s
static final int PATTERN_MILLISECOND          =  8; // S
static final int PATTERN_DAY_OF_WEEK          =  9; // E
static final int PATTERN_DAY_OF_YEAR          = 10; // D
static final int PATTERN_DAY_OF_WEEK_IN_MONTH = 11; // F
static final int PATTERN_WEEK_OF_YEAR         = 12; // w
static final int PATTERN_WEEK_OF_MONTH        = 13; // W
static final int PATTERN_AM_PM                = 14; // a
static final int PATTERN_HOUR1                = 15; // h
static final int PATTERN_HOUR0                = 16; // K
static final int PATTERN_ZONE_NAME            = 17; // z
static final int PATTERN_ZONE_VALUE           = 18; // Z
static final int PATTERN_WEEK_YEAR            = 19; // Y
static final int PATTERN_ISO_DAY_OF_WEEK      = 20; // u
static final int PATTERN_ISO_ZONE             = 21; // X
static final int PATTERN_MONTH_STANDALONE     = 22; // L

and here you can find the Y pattern element (the name is readable clear):

static final int PATTERN_WEEK_YEAR            = 19; // Y

*_WEEK_YEAR provides some confuse if we expecting single YEAR (following naming convention PATTERN_YEAR). Also we can find

static final int PATTERN_YEAR                 =  1; // y

Difference of meaning we can search in internet (in wiki for example). But what is the difference of using it in code? Continuing the check of using constants in SimpleDateFormat we can detect that PATTERN_WEEK_YEAR and PATTERN_YEAR are used in similar way for almost all cases. But with small logical difference (just find elements using in DateFormatSymbols.java)... and as result we'll be sure that code provides same to wiki terming meaning.


...following this java investigation way (using java doc and sources) we can clarify almost all questions without additional help with getting deep JDK knowledges.

like image 40
Sergii Avatar answered Jan 14 '23 14:01

Sergii