Java字符串转日期转换

1007

在Java中,将格式为“January 2, 2010” 的 String 转换为 Date 的最佳方法是什么?

最终,我想将月份、日期和年份分解为整数,以便可以使用。

Date date = new Date();
date.setMonth()..
date.setYear()..
date.setDay()..
date.setlong currentTime = date.getTime();

把日期转换成时间。


16
请注意,许多答案忽略了地区和时区等细节。在使用任何这些类之前,建议仔细阅读Date、Calendar、TimeZone、Locale和SimpleDateFormat类的文档。 - Kristopher Johnson
2
可能是 将字符串转换为 java.util.Date 的重复问题。 - Ankur
7
Java 8提供了一个新的日期/时间API。如果您正在使用Java 8(或更新版本),您应该查看这个答案:https://dev59.com/SW855IYBdhLWcg3wp2N0#22180505 - micha
2
Date 的所有 setter 方法都已被弃用。 - Raedwald
7
请注意,诸如java.util.Datejava.util.Calendarjava.text.SimpleTextFormat等老旧的日期时间类现已被视为遗留系统,取而代之的是Java.time类。请参见Oracle的教程 - Basil Bourque
显示剩余2条评论
17个回答

1838
那是一种困难的方式,而且自Java 1.1 (1997)以来,那些java.util.Date设置方法已经被弃用。此外,自从Java 8(2014)引入java.time API以来,整个java.util.Date类已被实际弃用(不建议使用)。
只需使用匹配输入字符串的模式(教程在这里),使用DateTimeFormatter格式化日期即可。
对于“2010年1月2日”作为输入字符串的特定情况:
1. “January”是完整的文本月份,因此使用MMMM模式。 2. “2”是短的日期,因此使用d模式。 3. “2010”是四位数字年份,因此使用yyyy模式。
String string = "January 2, 2010";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MMMM d, yyyy", Locale.ENGLISH);
LocalDate date = LocalDate.parse(string, formatter);
System.out.println(date); // 2010-01-02

注意:如果您的格式模式同时包含时间部分,则应使用LocalDateTime#parse(text, formatter)而不是LocalDate#parse(text, formatter)。如果您的格式模式同时包含时区,那么请使用ZonedDateTime#parse(text, formatter)
以下是从the javadoc中提取的相关内容,列出了所有可用的格式模式:
符号 含义 表示 示例
G 纪元 文本 AD; Anno Domini; A
u 年份 年份 2004; 04
y 纪元内年份 年份 2004; 04
D 一年中的第几天 数字 189
M/L 月份 数字/文本 7; 07; Jul; July; J
d 月份中的第几天 数字 10
Q/q 季度 数字/文本 3; 03; Q3; 3rd quarter
Y 基于周的年份 年份 1996; 96
w 基于周的年份中的第几周 数字 27
W 一个月中的第几周 数字 4
E 星期几 文本 Tue; Tuesday; T
e/c 本地化的星期几 数字/文本 2; 02; Tue; Tuesday; T
F 一个月中的第几周 数字 3
a 上午/下午 文本 PM
h 上午/下午的小时数(1-12) 数字 12
K 上午/下午的小时数(0-11) 数字 0
k 24小时制的小时数(1-24) 数字 0
H 24小时制的小时数(0-23) 数字 0
m 分钟数 数字 30
s 秒数 数字 55
S 毫秒数 小数 978
A 当天的毫秒数 数字 1234
n 纳秒数 数字 987654
请注意,它有几个预定义的格式化程序用于更流行的模式。因此,您可以使用DateTimeFormatter.RFC_1123_DATE_TIME而不是例如DateTimeFormatter.ofPattern("EEE,d MMM yyyy HH:mm:ss Z",Locale.ENGLISH);。这是可能的,因为与SimpleDateFormat相反,它们是线程安全的。因此,如果需要,您也可以定义自己的格式化程序。
针对特定的输入字符串格式,您不需要使用显式的DateTimeFormatter:标准的ISO 8601日期,例如2016-09-26T17:44:57Z,可以直接使用LocalDateTime#parse(text)进行解析,因为它已经使用了ISO_LOCAL_DATE_TIME格式化程序。类似地,LocalDate#parse(text)解析没有时间组件的ISO日期(请参见ISO_LOCAL_DATE),而ZonedDateTime#parse(text)解析带有偏移和时区的ISO日期(请参见ISO_ZONED_DATE_TIME)。

Java 8之前

如果您还没有升级到Java 8,或者被迫使用java.util.Date,那么可以使用SimpleDateFormat根据输入字符串匹配格式模式来格式化日期。

String string = "January 2, 2010";
DateFormat format = new SimpleDateFormat("MMMM d, yyyy", Locale.ENGLISH);
Date date = format.parse(string);
System.out.println(date); // Sat Jan 02 00:00:00 GMT 2010

请注意显式指定Locale参数的重要性。如果省略它,则会使用默认区域设置,该区域设置不一定是输入字符串中使用的英语月份名称。如果区域设置与输入字符串不匹配,则即使格式模式似乎有效,您也会混淆地获得java.text.ParseException
以下是从javadoc中提取的相关内容,列出了所有可用的格式模式:
字母 日期或时间组件 呈现方式 示例
G 纪元标识符 文本 公元
y 年份 1996; 96
Y 周年 2009; 09
M/L 年中月份 七月; Jul; 07
w 年中周数 数字 27
W 月中周数 数字 2
D 年中天数 数字 189
d 月中天数 数字 10
F 月中第几个星期几 数字 2
E 星期中天数 文本 星期二; 周二
u 星期中的天数 数字 1
a 上午/下午标识符 文本 下午
H 一天中的小时数(0-23) 数字 0
k 一天中的小时数(1-24) 数字 24
K 上午/下午的小时数(0-11) 数字 0
h 上午/下午的小时数(1-12) 数字 12
m 小时中的分钟数 数字 30
s 分钟中的秒数 数字 55
S 毫秒数 数字 978
z 时区 通用时区 太平洋标准时间; PST; GMT-08:00
Z 时区 RFC 822格式的时区 -0800
X 时区 ISO 8601格式的时区 -08; -0800; -08:00
请注意,模式区分大小写,基于文本的四个或更多字符的模式表示完整形式;否则,如果有可用的短形式或缩写形式,则使用。例如,MMMMM 或更多是不必要的。
以下是一些有效的 SimpleDateFormat 模式示例,用于将给定字符串解析为日期:
输入字符串 模式
2001.07.04 AD at 12:08:56 PDT yyyy.MM.dd G 'at' HH:mm:ss z
星期三, 7月 4, '01 EEE, MMM d, ''yy
下午12:08 h:mm a
下午12点整, 太平洋夏令时 hh 'o''clock' a, zzzz
下午0:08, PDT K:mm a, z
02001年7月4日 AD 下午12:08 yyyyy.MMMM.dd GGG hh:mm aaa
星期三, 4 7月 2001 12:08:56 -0700 EEE, d MMM yyyy HH:mm:ss Z
010704120856-0700 yyMMddHHmmssZ
2001-07-04T12:08:56.235-0700 yyyy-MM-dd'T'HH:mm:ss.SSSZ
2001-07-04T12:08:56.235-07:00 yyyy-MM-dd'T'HH:mm:ss.SSSXXX
2001-W27-3 YYYY-'W'ww-u

一个重要的注意事项是,SimpleDateFormat非线程安全的。换句话说,您不应该将其声明和分配为静态或实例变量,然后从不同的方法/线程中重用它。您应该始终在方法本地范围内全新创建它。


嗨,在选项 O 中,如何让它打印 UTC+08:00 而不是 GMT+08:00。我找不到任何示例。 - theGamblerRises
有很多方法可以解析日期,这里是DateFormat.parse的各种用例。 - drorw
嗨,BalusC,我有一个字符串20-JUN-16 12.00.00.000000000 AM,需要帮助将其转换为日期。非常感谢您的帮助! - Suresh
如果我们需要使用API 23及以下的版本怎么办?所有这些“parse”方法都是针对Java8+的。 - J A S K I E R
我得到了一个给定日期和格式的意外日期格式。比如,日期字符串是 2022-11-01 08:32:08,这个日期使用 Carbon Laravel 的格式 Carbon::now()->format('Y-m-d H:i:s') 进行保存,在 Java 中使用 (new SimpleDateFormat("y-m-d H:m:s")).parse(date) 进行解析,得到 Mon Jan 31 14:14:15 GMT+05:00 2022。我希望它与输入的格式相同。已经尝试使用 yyyy-mm-dd h:m:s 进行检查,但没有得到想要的结果。对于长评论表示抱歉。 - Maseed
显示剩余2条评论

83

啊,又是Java日期讨论。为了处理日期操作,我们使用DateCalendarGregorianCalendarSimpleDateFormat。例如,以您的一月日期作为输入:

Calendar mydate = new GregorianCalendar();
String mystring = "January 2, 2010";
Date thedate = new SimpleDateFormat("MMMM d, yyyy", Locale.ENGLISH).parse(mystring);
mydate.setTime(thedate);
//breakdown
System.out.println("mydate -> "+mydate);
System.out.println("year   -> "+mydate.get(Calendar.YEAR));
System.out.println("month  -> "+mydate.get(Calendar.MONTH));
System.out.println("dom    -> "+mydate.get(Calendar.DAY_OF_MONTH));
System.out.println("dow    -> "+mydate.get(Calendar.DAY_OF_WEEK));
System.out.println("hour   -> "+mydate.get(Calendar.HOUR));
System.out.println("minute -> "+mydate.get(Calendar.MINUTE));
System.out.println("second -> "+mydate.get(Calendar.SECOND));
System.out.println("milli  -> "+mydate.get(Calendar.MILLISECOND));
System.out.println("ampm   -> "+mydate.get(Calendar.AM_PM));
System.out.println("hod    -> "+mydate.get(Calendar.HOUR_OF_DAY));

然后你可以用类似下面的方式操纵它:

Calendar now = Calendar.getInstance();
mydate.set(Calendar.YEAR,2009);
mydate.set(Calendar.MONTH,Calendar.FEBRUARY);
mydate.set(Calendar.DAY_OF_MONTH,25);
mydate.set(Calendar.HOUR_OF_DAY,now.get(Calendar.HOUR_OF_DAY));
mydate.set(Calendar.MINUTE,now.get(Calendar.MINUTE));
mydate.set(Calendar.SECOND,now.get(Calendar.SECOND));
// or with one statement
//mydate.set(2009, Calendar.FEBRUARY, 25, now.get(Calendar.HOUR_OF_DAY), now.get(Calendar.MINUTE), now.get(Calendar.SECOND));
System.out.println("mydate -> "+mydate);
System.out.println("year   -> "+mydate.get(Calendar.YEAR));
System.out.println("month  -> "+mydate.get(Calendar.MONTH));
System.out.println("dom    -> "+mydate.get(Calendar.DAY_OF_MONTH));
System.out.println("dow    -> "+mydate.get(Calendar.DAY_OF_WEEK));
System.out.println("hour   -> "+mydate.get(Calendar.HOUR));
System.out.println("minute -> "+mydate.get(Calendar.MINUTE));
System.out.println("second -> "+mydate.get(Calendar.SECOND));
System.out.println("milli  -> "+mydate.get(Calendar.MILLISECOND));
System.out.println("ampm   -> "+mydate.get(Calendar.AM_PM));
System.out.println("hod    -> "+mydate.get(Calendar.HOUR_OF_DAY));

18
别忘了,一月是第0个月。 - Nicolas Zozol

67
String str_date = "11-June-07";
DateFormat formatter = new SimpleDateFormat("dd-MMM-yy");
Date date = formatter.parse(str_date);

17
为什么要将声明和定义分开(尽管第一个变量没有这样做)的目的是什么? - Peter Mortensen

48

在Java 8中,我们获得了一个新的日期/时间API(JSR 310)。

以下方式可用于在Java 8中解析日期,而不依赖于Joda-Time

 String str = "January 2nd, 2010";

// if we 2nd even we have changed in pattern also it is not working please workout with 2nd 
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MMMM Q, yyyy", Locale.ENGLISH);
LocalDate date = LocalDate.parse(str, formatter);

// access date fields
int year = date.getYear(); // 2010
int day = date.getDayOfMonth(); // 2
Month month = date.getMonth(); // JANUARY
int monthAsInt = month.getValue(); // 1

LocalDate 是标准的 Java 8 类,用于表示日期(不包含时间)。如果您想解析包含日期和时间信息的值,则应使用 LocalDateTime。对于带有时区的值,请使用 ZonedDateTime。两者都提供了类似于 LocalDateparse() 方法:

LocalDateTime dateWithTime = LocalDateTime.parse(strWithDateAndTime, dateTimeFormatter);
ZonedDateTime zoned = ZonedDateTime.parse(strWithTimeZone, zoneFormatter);

以下是来自DateTimeFormatter Javadoc的列表格式化字符:
All letters 'A' to 'Z' and 'a' to 'z' are reserved as pattern letters. 
The following pattern letters are defined:

Symbol  Meaning                     Presentation      Examples
------  -------                     ------------      -------
 G       era                         text              AD; Anno Domini; A
 u       year                        year              2004; 04
 y       year-of-era                 year              2004; 04
 D       day-of-year                 number            189
 M/L     month-of-year               number/text       7; 07; Jul; July; J
 d       day-of-month                number            10

 Q/q     quarter-of-year             number/text       3; 03; Q3; 3rd quarter
 Y       week-based-year             year              1996; 96
 w       week-of-week-based-year     number            27
 W       week-of-month               number            4
 E       day-of-week                 text              Tue; Tuesday; T
 e/c     localized day-of-week       number/text       2; 02; Tue; Tuesday; T
 F       week-of-month               number            3

 a       am-pm-of-day                text              PM
 h       clock-hour-of-am-pm (1-12)  number            12
 K       hour-of-am-pm (0-11)        number            0
 k       clock-hour-of-am-pm (1-24)  number            0

 H       hour-of-day (0-23)          number            0
 m       minute-of-hour              number            30
 s       second-of-minute            number            55
 S       fraction-of-second          fraction          978
 A       milli-of-day                number            1234
 n       nano-of-second              number            987654321
 N       nano-of-day                 number            1234000000

 V       time-zone ID                zone-id           America/Los_Angeles; Z; -08:30
 z       time-zone name              zone-name         Pacific Standard Time; PST
 O       localized zone-offset       offset-O          GMT+8; GMT+08:00; UTC-08:00;
 X       zone-offset 'Z' for zero    offset-X          Z; -08; -0830; -08:30; -083015; -08:30:15;
 x       zone-offset                 offset-x          +0000; -08; -0830; -08:30; -083015; -08:30:15;
 Z       zone-offset                 offset-Z          +0000; -0800; -08:00;

毫秒级别的时间是如何工作的?如果我使用LocalDateTime date = LocalDateTime.parse("20140920111713000",DateTimeFormatter.of‌​Pattern("yyyyMMddHHm‌​mssSSS"));,它会失败,但如果我使用LocalDateTime date = LocalDateTime.parse("20140920111713.000",DateTimeFormatter.o‌​fPattern("yyyyMMddHH‌​mmss.SSS"));,它就可以正常工作。 - reos

31

虽然有些答案从技术上来说是正确的,但并不可取。

java.util.Date和Calendar类因设计和实现上的缺陷而臭名昭著,应避免使用。幸运的是,我们有两个其他优秀的日期时间库可供选择:
- Joda-Time:这个流行的开源免费库可以在多个Java版本中使用。许多它的使用示例可以在StackOverflow上找到。阅读其中一些将有助于您快速掌握它的用法。 - java.time.* package:这组新的类受Joda-Time启发并由JSR 310定义。这些类内置于Java 8中。正在进行一个项目,将这些类向后移植到Java 7,但该移植不得到Oracle的支持。
正如Kristopher Johnson在他对问题的评论中正确指出的那样,其他答案忽略了以下重要问题:
- 时分秒(Date具有日期部分和时分秒部分)。 - 时区:一天的开始取决于时区。如果未指定时区,则应用JVM的默认时区。这意味着在其他计算机上运行或更改时区设置时,代码的行为可能会发生变化。这可能不是您想要的。 - Locale:Locale的语言指定如何解释解析过程中遇到的单词(月份和星期几的名称)。 (BalusC的答案正确处理此问题。)此外,Locale会影响某些格式化程序在生成日期时间的字符串表示时的输出。

Joda-Time

以下是有关Joda-Time的一些注释。

时区

Joda-Time中,DateTime对象真正知道自己所分配的时区。这与java.util.Date类不同,后者似乎具有时区,但实际上并没有。

请注意下面的示例代码中,我们将一个时区对象传递给解析字符串的格式化程序。该时区用于将该日期时间解释为发生在该时区中。因此,您需要考虑和确定该字符串输入所代表的时区。

由于输入字符串中没有时间部分,Joda-Time会将指定时区的当天第一时刻作为当天的时间。通常这意味着00:00:00,但由于夏令时(DST)或其他异常情况,这并不总是正确的。顺便说一句,您可以通过调用withTimeAtStartOfDay对任何DateTime实例执行相同的操作。

格式化模式

格式化程序模式中使用的字符在Joda-Time中与java.util.Date/Calendar中的字符类似,但并不完全相同。请仔细阅读文档。

不变性

我们通常在Joda-Time中使用不可变类。我们不修改现有的日期时间对象,而是调用方法创建一个新的实例,该实例基于其他对象,其中大多数方面都被复制,除了所需的更改。下面是最后一行中调用withZone的示例。不变性有助于使Joda-Time非常线程安全,并且还可以使某些工作更加清晰。

转换

你需要使用java.util.Date对象与其他不了解Joda-Time对象的类/框架一起使用。幸运的是,来回转换非常容易。
从java.util.Date对象(这里命名为date)转换为Joda-Time DateTime...
org.joda.time.DateTime dateTime = new DateTime( date, timeZone );

从Joda-Time转换为java.util.Date对象的另一种方法是...
java.util.Date date = dateTime.toDate();

示例代码

String input = "January 2, 2010";

java.util.Locale locale = java.util.Locale.US;
DateTimeZone timeZone = DateTimeZone.forID( "Pacific/Honolulu" ); // Arbitrarily chosen for example.
DateTimeFormatter formatter = DateTimeFormat.forPattern( "MMMM d, yyyy" ).withZone( timeZone ).withLocale( locale );
DateTime dateTime = formatter.parseDateTime( input );

System.out.println( "dateTime: " + dateTime );
System.out.println( "dateTime in UTC/GMT: " + dateTime.withZone( DateTimeZone.UTC ) );

当运行时...
dateTime: 2010-01-02T00:00:00.000-10:00
dateTime in UTC/GMT: 2010-01-02T10:00:00.000Z

22
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
Date date;
try {
    date = dateFormat.parse("2013-12-4");
    System.out.println(date.toString()); // Wed Dec 04 00:00:00 CST 2013

    String output = dateFormat.format(date);
    System.out.println(output); // 2013-12-04
} 
catch (ParseException e) {
    e.printStackTrace();
}

对我来说它运行良好。


5
输出结果将为“Wed Dec 04 00:00:00 GST 2013”,而不是“2013-12-04”。 - Shams
应该在 ParseException try/catch 中包装。 - svarog
1
这不会按预期返回值。请在建议之前进行测试。 - lifemoveson

20

在处理SimpleDateFormat类时,需要记住Date不是线程安全的,不能在多个线程之间共享单个Date对象。

此外,“m”和“M”之间存在很大差异,小写用于表示分钟,而大写用于表示月份。同样,“d”和“D”也是如此。这可能会导致微妙的错误,这些错误经常被忽视。有关详细信息,请参见JavadocJava中将字符串转换为日期的指南


1
这个大小写的差别让我浪费了5分钟的时间,感谢你指出来 :) - Buffalo
我很惊讶没有其他人提到这一点。在Web应用程序(或任何其他多线程应用程序)中使用SimpleDateFormat是一个大忌讳。直到Java 7,我一直在使用“FastDateFormat”。 - JackLThornton

11

你可以使用SimpleDateFormat将字符串转换为日期

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
String strDate = "2000-01-01";
Date date = sdf.parse(strDate);


1
SimpleDateFormat的解析方法返回一个日期对象,那么为什么要使用Date构造函数呢? - nespapu

7

我们使用了两个简单的格式化程序:

  1. 我们想要什么日期格式?
  2. 实际上存在什么日期格式?

我们将完整的日期解析为时间格式:

date="2016-05-06 16:40:32";

public static String setDateParsing(String date) throws ParseException {

    // This is the format date we want
    DateFormat mSDF = new SimpleDateFormat("hh:mm a");

    // This format date is actually present
    SimpleDateFormat formatter = new SimpleDateFormat("yyyy-mm-dd hh:mm");
    return mSDF.format(formatter.parse(date));
}

6
此外,一些客户端技术(如 GWT)不支持 SimpleDateFormat。建议使用 Calendar.getInstance(),如果需要比较两个日期,则可以选择使用长日期。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接