为什么 SimpleDateFormat 会更改日期?

Why is SimpleDateFormat changing the date?

给定以下代码:

[...]

    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());
    }

[...]

之前: Sat Aug 19 18:26:11 BST 2017

之后: Sat Jan 07 17:26:11 GMT 2017

为什么日期变了?

实际上是同一个日期,英国夏令时(BST)正好比格林威治标准时间早一小时。

另请注意,'Y' 是一年中的第几周,您要使用的是 'y'

https://docs.oracle.com/javase/8/docs/api/java/text/SimpleDateFormat.html

大写的 Y 用于 "week year",它有 364 或 371 天,而不是通常的 365 或 366。小写的 y(由 Date#toString) 一切正常:

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());
}

输出:

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

See on ideone.com

如评论中所述,在解析 dateString 时确保包含 Locale.US,因为它已硬编码在 Date#toString 中。有关详细信息,请参阅

首先我要说我完全同意 Marvins 但我希望有人会对更多技术细节感兴趣。

关注 java doc SimpleDateFormat,您找不到 YYYY 的示例。但它有效并且验证通过。深入检查:

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 + "'");
        }
        ...
    }
    ...
}

检查允许的模式符号(在验证条件中使用的)DateFormatSymbols.patternChars:

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

Y 是有效的模式元素,它是什么意思(让我们检查相同 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

在这里您可以找到 Y 模式元素(名称清晰可读):

static final int PATTERN_WEEK_YEAR            = 19; // Y
如果我们期望单个 YEAR(遵循命名约定 PATTERN_YEAR),

*_WEEK_YEAR 会造成一些混淆。我们还可以找到

static final int PATTERN_YEAR                 =  1; // y

意义的差异我们可以在网上搜索(例如在wiki中)。但是在代码中使用它有什么区别呢?继续检查在 SimpleDateFormat 中使用常量,我们可以检测到 PATTERN_WEEK_YEARPATTERN_YEAR 几乎在所有情况下都以类似的方式使用。但是逻辑上的差异很小(只需找到 DateFormatSymbols.java 中使用的元素)...结果我们将确保代码提供与 wiki 术语相同的含义。


...按照这种 java 调查方式(使用 java 文档和来源),我们可以澄清几乎所有问题,而无需额外帮助获得深入的 JDK 知识。