将字符串转换为日期(CEST 工作正常,GMT+02:00 不工作)
Convert string to date (CEST works fine, GMT+02:00 doesn't work)
问题
为什么我的 Android 应用无法解析 String str1= "Tue Jun 20 15:56:29 CEST 2017"
?
我发现了一些类似的问题,但其中 none 对我有所帮助。
旁注
在我的项目中,我有一些 Java 应用程序在计算机上 运行 和一些 Android 应用程序。他们能够相互交流。
消息中有时间戳。但是,我的 Java 应用程序正在以 String str1= "Tue Jun 20 15:56:29 CEST 2017"
格式发送时间戳,而我的 Android 应用程序发送 String str2 = "Tue Jun 20 13:40:37 GMT+02:00 2017"
格式的时间戳。要保存包含时间的消息,我必须将传入时间解析为日期。
我的Android-App
在我的 Android 应用程序中,我无法正确解析 String str1= "Tue Jun 20 15:56:29 CEST 2017"
:
java.text.ParseException: Unparseable date: "Tue Jun 20 15:56:29 CEST 2017"
String str2 = "Tue Jun 20 13:40:37 GMT+02:00 2017"
工作正常。
代码:
String str1 = "Tue Jun 20 14:53:08 CEST 2017";
SimpleDateFormat formatter = new SimpleDateFormat("EE MMM dd HH:mm:ss zzzz yyyy", Locale.US);
try {
Date date = formatter.parse(str1);
} catch (ParseException e) {
e.printStackTrace();
}
// test
String str2 = "Tue Jun 20 13:40:37 GMT+02:00 2017";
formatter = new SimpleDateFormat("EE MMM dd HH:mm:ss zzzz yyyy", Locale.US);
try {
Date date = formatter.parse(str2);
} catch (ParseException e) {
e.printStackTrace();
}
我的Java-App
但是,我的 Java 应用程序可以正确解析这两个字符串。
代码:
String str = "Tue Jun 20 14:53:08 CEST 2017";
SimpleDateFormat formatter = new SimpleDateFormat("EE MMM dd HH:mm:ss zzzz yyyy", Locale.US);
try {
Date date = formatter.parse(str);
} catch (ParseException e) {
e.printStackTrace();
}
// test
str = "Tue Jun 20 13:40:37 GMT+02:00 2017";
formatter = new SimpleDateFormat("EE MMM dd HH:mm:ss zzzz yyyy", Locale.US);
try {
Date date = formatter.parse(str);
} catch (ParseException e) {
e.printStackTrace();
}
ThreeTen 解决方案
字符串到 LocalDateTime
要转换传入的字符串,我使用以下代码:
String time = "Mon Jun 26 15:42:51 GMT 2017";
DateTimeFormatter gmtDateTimeFormatter = DateTimeFormatter.ofPattern("EE MMM dd HH:mm:ss 'GMT' yyyy", Locale.ENGLISH));
LocalDateTime timestamp = LocalDateTime.parse(time, gmtDateTimeFormatter);
LocalDateTime 到 String
为了将我的 LocalDateTime 转换为字符串,我使用了这个:
LocalDateTime timestamp = LocalDateTime.now();
DateTimeFormatter gmtDateTimeFormatter = DateTimeFormatter.ofPattern("EE MMM dd HH:mm:ss 'GMT' yyyy", Locale.ENGLISH));
String time = gmtDateTimeFormatter.format(timestamp);
也许 Android 处理 zzzz
模式的方式不同(可能 Java 的实现比 Android 处理得更好,所以它 "guesses" 正确的时区,Android 没有)。我不知道。
无论如何,我可以建议您避免使用那些旧的 classes 吗?这些旧的 classes(Date
、Calendar
和 SimpleDateFormat
)有 lots of problems and design issues,它们正在被新的 APIs 取代。
如果您正在使用 Java 8,请考虑使用 new java.time API. It's easier, less bugged and less error-prone than the old APIs.
如果您使用 Java <= 7,您可以使用 ThreeTen Backport, a great backport for Java 8's new date/time classes. And for Android, there's the ThreeTenABP (more on how to use it ).
下面的代码适用于两者。
唯一的区别是包名称(在 Java 8 中是 java.time
,在 ThreeTen Backport(或 Android 的 ThreeTenABP)中是 org.threeten.bp
),但是 classes 和方法 names 相同。
要解析这两种格式,您可以使用带有可选部分的 DateTimeFormatter
。这是因为 CEST
是一个时区短名称,而 GMT+02:00
是一个 UTC 偏移量,所以如果你想用相同的格式化程序解析两者,你需要为每种格式使用一个可选部分。
另一个细节是 CET
或 CEST
等短名称是 ambiguous and not standard. The new API uses IANA timezones names(始终采用 Continent/City
格式,例如 America/Sao_Paulo
或 Europe/Berlin
).
因此,您需要选择一个适合您需要的时区。在下面的示例中,我刚刚选择了 CEST (Europe/Berlin
) 中的时区,但您可以根据需要更改它 - 您可以使用 ZoneId.getAvailableZoneIds()
.[ 获取所有名称的列表。 =51=]
由于新的 API 没有解析 CEST
(因为它的歧义),我需要创建一个带有首选时区的集合以便正确解析输入:
// when parsing, if finds ambiguous CET or CEST, it uses Berlin as prefered timezone
Set<ZoneId> set = new HashSet<>();
set.add(ZoneId.of("Europe/Berlin"));
DateTimeFormatter fmt = new DateTimeFormatterBuilder()
// your pattern (weekday, month, day, hour/minute/second)
.appendPattern("EE MMM dd HH:mm:ss ")
// optional timezone short name (like "CST" or "CEST")
.optionalStart().appendZoneText(TextStyle.SHORT, set).optionalEnd()
// optional GMT offset (like "GMT+02:00")
.optionalStart().appendPattern("OOOO").optionalEnd()
// year
.appendPattern(" yyyy")
// create formatter (using English locale to make sure it parses weekday and month names correctly)
.toFormatter(Locale.US);
要解析 Tue Jun 20 14:53:08 CEST 2017
,只需使用格式化程序:
ZonedDateTime z1 = ZonedDateTime.parse("Tue Jun 20 14:53:08 CEST 2017", fmt);
System.out.println(z1);
输出为:
2017-06-20T14:53:08+02:00[Europe/Berlin]
请注意,根据我们创建的集合,CEST
被映射到 Europe/Berlin
。
要解析Tue Jun 20 13:40:37 GMT+02:00 2017
,我们可以使用相同的格式化程序。但是 GMT+02:00
可以在很多不同的地区,所以 API 不能将它映射到一个时区。要将其转换为正确的时区,我需要使用 withZoneSameInstant()
方法:
// parse with UTC offset
ZonedDateTime z2 = ZonedDateTime.parse("Tue Jun 20 13:40:37 GMT+02:00 2017", fmt)
// convert to Berlin timezone
.withZoneSameInstant(ZoneId.of("Europe/Berlin"));
System.out.println(z2);
输出为:
2017-06-20T13:40:37+02:00[Europe/Berlin]
PS: 第一种情况 (z1
) 在 Java 8 中有效,但在 ThreeTen Backport 中它没有将时区设置为柏林。要修复它,只需调用 .withZoneSameInstant(ZoneId.of("Europe/Berlin"))
就像我们对 z2
所做的那样。
如果您仍然需要使用 java.util.Date
,您可以转换为新的 API。
在java.time
中添加了新方法Date
class:
// convert ZonedDateTime to Date
Date date = Date.from(z1.toInstant());
// convert back to ZonedDateTime (using Berlin timezone)
ZonedDateTime z = date.toInstant().atZone(ZoneId.of("Europe/Berlin"));
在 ThreeTen 向后移植(和 Android)中,您可以使用 org.threeten.bp.DateTimeUtils
class:
// convert ZonedDateTime to Date
Date date = DateTimeUtils.toDate(z1.toInstant());
// convert back to ZonedDateTime (using Berlin timezone)
ZonedDateTime z = DateTimeUtils.toInstant(date).atZone(ZoneId.of("Europe/Berlin"));
java.time
java.util
日期时间 API 及其格式 API、SimpleDateFormat
已过时且容易出错。建议完全停止使用它们并切换到 modern Date-Time API*.
此外,下面引用的是来自 home page of Joda-Time 的通知:
Note that from Java SE 8 onwards, users are asked to migrate to java.time (JSR-310) - a core part of the JDK which replaces this project.
不要为时区使用固定文本:
不要像您所做的那样为时区使用固定文本(例如 'GMT'
),因为该方法对于其他语言环境可能会失败。
解决方案使用 java.time
,现代日期时间 API:
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
public class Main {
public static void main(String[] args) {
// Test
System.out.println(parse("Tue Jun 20 14:53:08 CEST 2017"));
System.out.println(parse("Tue Jun 20 13:40:37 GMT+02:00 2017"));
}
static ZonedDateTime parse(String strDateTime) {
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("E MMM d H:m:s z u", Locale.ENGLISH);
return ZonedDateTime.parse(strDateTime, dtf);
}
}
输出:
2017-06-20T14:53:08+02:00[Europe/Paris]
2017-06-20T13:40:37+02:00[GMT+02:00]
了解有关现代日期时间 API 的更多信息
* 无论出于何种原因,如果您必须坚持Java 6 或Java 7,您可以使用ThreeTen-Backport which backports most of the java.time functionality to Java 6 & 7. If you are working for an Android project and your Android API level is still not compliant with Java-8, check Java 8+ APIs available through desugaring and 。
问题
为什么我的 Android 应用无法解析 String str1= "Tue Jun 20 15:56:29 CEST 2017"
?
我发现了一些类似的问题,但其中 none 对我有所帮助。
旁注
在我的项目中,我有一些 Java 应用程序在计算机上 运行 和一些 Android 应用程序。他们能够相互交流。
消息中有时间戳。但是,我的 Java 应用程序正在以 String str1= "Tue Jun 20 15:56:29 CEST 2017"
格式发送时间戳,而我的 Android 应用程序发送 String str2 = "Tue Jun 20 13:40:37 GMT+02:00 2017"
格式的时间戳。要保存包含时间的消息,我必须将传入时间解析为日期。
我的Android-App
在我的 Android 应用程序中,我无法正确解析 String str1= "Tue Jun 20 15:56:29 CEST 2017"
:
java.text.ParseException: Unparseable date: "Tue Jun 20 15:56:29 CEST 2017"
String str2 = "Tue Jun 20 13:40:37 GMT+02:00 2017"
工作正常。
代码:
String str1 = "Tue Jun 20 14:53:08 CEST 2017";
SimpleDateFormat formatter = new SimpleDateFormat("EE MMM dd HH:mm:ss zzzz yyyy", Locale.US);
try {
Date date = formatter.parse(str1);
} catch (ParseException e) {
e.printStackTrace();
}
// test
String str2 = "Tue Jun 20 13:40:37 GMT+02:00 2017";
formatter = new SimpleDateFormat("EE MMM dd HH:mm:ss zzzz yyyy", Locale.US);
try {
Date date = formatter.parse(str2);
} catch (ParseException e) {
e.printStackTrace();
}
我的Java-App
但是,我的 Java 应用程序可以正确解析这两个字符串。
代码:
String str = "Tue Jun 20 14:53:08 CEST 2017";
SimpleDateFormat formatter = new SimpleDateFormat("EE MMM dd HH:mm:ss zzzz yyyy", Locale.US);
try {
Date date = formatter.parse(str);
} catch (ParseException e) {
e.printStackTrace();
}
// test
str = "Tue Jun 20 13:40:37 GMT+02:00 2017";
formatter = new SimpleDateFormat("EE MMM dd HH:mm:ss zzzz yyyy", Locale.US);
try {
Date date = formatter.parse(str);
} catch (ParseException e) {
e.printStackTrace();
}
ThreeTen 解决方案
字符串到 LocalDateTime
要转换传入的字符串,我使用以下代码:
String time = "Mon Jun 26 15:42:51 GMT 2017";
DateTimeFormatter gmtDateTimeFormatter = DateTimeFormatter.ofPattern("EE MMM dd HH:mm:ss 'GMT' yyyy", Locale.ENGLISH));
LocalDateTime timestamp = LocalDateTime.parse(time, gmtDateTimeFormatter);
LocalDateTime 到 String
为了将我的 LocalDateTime 转换为字符串,我使用了这个:
LocalDateTime timestamp = LocalDateTime.now();
DateTimeFormatter gmtDateTimeFormatter = DateTimeFormatter.ofPattern("EE MMM dd HH:mm:ss 'GMT' yyyy", Locale.ENGLISH));
String time = gmtDateTimeFormatter.format(timestamp);
也许 Android 处理 zzzz
模式的方式不同(可能 Java 的实现比 Android 处理得更好,所以它 "guesses" 正确的时区,Android 没有)。我不知道。
无论如何,我可以建议您避免使用那些旧的 classes 吗?这些旧的 classes(Date
、Calendar
和 SimpleDateFormat
)有 lots of problems and design issues,它们正在被新的 APIs 取代。
如果您正在使用 Java 8,请考虑使用 new java.time API. It's easier, less bugged and less error-prone than the old APIs.
如果您使用 Java <= 7,您可以使用 ThreeTen Backport, a great backport for Java 8's new date/time classes. And for Android, there's the ThreeTenABP (more on how to use it
下面的代码适用于两者。
唯一的区别是包名称(在 Java 8 中是 java.time
,在 ThreeTen Backport(或 Android 的 ThreeTenABP)中是 org.threeten.bp
),但是 classes 和方法 names 相同。
要解析这两种格式,您可以使用带有可选部分的 DateTimeFormatter
。这是因为 CEST
是一个时区短名称,而 GMT+02:00
是一个 UTC 偏移量,所以如果你想用相同的格式化程序解析两者,你需要为每种格式使用一个可选部分。
另一个细节是 CET
或 CEST
等短名称是 ambiguous and not standard. The new API uses IANA timezones names(始终采用 Continent/City
格式,例如 America/Sao_Paulo
或 Europe/Berlin
).
因此,您需要选择一个适合您需要的时区。在下面的示例中,我刚刚选择了 CEST (Europe/Berlin
) 中的时区,但您可以根据需要更改它 - 您可以使用 ZoneId.getAvailableZoneIds()
.[ 获取所有名称的列表。 =51=]
由于新的 API 没有解析 CEST
(因为它的歧义),我需要创建一个带有首选时区的集合以便正确解析输入:
// when parsing, if finds ambiguous CET or CEST, it uses Berlin as prefered timezone
Set<ZoneId> set = new HashSet<>();
set.add(ZoneId.of("Europe/Berlin"));
DateTimeFormatter fmt = new DateTimeFormatterBuilder()
// your pattern (weekday, month, day, hour/minute/second)
.appendPattern("EE MMM dd HH:mm:ss ")
// optional timezone short name (like "CST" or "CEST")
.optionalStart().appendZoneText(TextStyle.SHORT, set).optionalEnd()
// optional GMT offset (like "GMT+02:00")
.optionalStart().appendPattern("OOOO").optionalEnd()
// year
.appendPattern(" yyyy")
// create formatter (using English locale to make sure it parses weekday and month names correctly)
.toFormatter(Locale.US);
要解析 Tue Jun 20 14:53:08 CEST 2017
,只需使用格式化程序:
ZonedDateTime z1 = ZonedDateTime.parse("Tue Jun 20 14:53:08 CEST 2017", fmt);
System.out.println(z1);
输出为:
2017-06-20T14:53:08+02:00[Europe/Berlin]
请注意,根据我们创建的集合,CEST
被映射到 Europe/Berlin
。
要解析Tue Jun 20 13:40:37 GMT+02:00 2017
,我们可以使用相同的格式化程序。但是 GMT+02:00
可以在很多不同的地区,所以 API 不能将它映射到一个时区。要将其转换为正确的时区,我需要使用 withZoneSameInstant()
方法:
// parse with UTC offset
ZonedDateTime z2 = ZonedDateTime.parse("Tue Jun 20 13:40:37 GMT+02:00 2017", fmt)
// convert to Berlin timezone
.withZoneSameInstant(ZoneId.of("Europe/Berlin"));
System.out.println(z2);
输出为:
2017-06-20T13:40:37+02:00[Europe/Berlin]
PS: 第一种情况 (z1
) 在 Java 8 中有效,但在 ThreeTen Backport 中它没有将时区设置为柏林。要修复它,只需调用 .withZoneSameInstant(ZoneId.of("Europe/Berlin"))
就像我们对 z2
所做的那样。
如果您仍然需要使用 java.util.Date
,您可以转换为新的 API。
在java.time
中添加了新方法Date
class:
// convert ZonedDateTime to Date
Date date = Date.from(z1.toInstant());
// convert back to ZonedDateTime (using Berlin timezone)
ZonedDateTime z = date.toInstant().atZone(ZoneId.of("Europe/Berlin"));
在 ThreeTen 向后移植(和 Android)中,您可以使用 org.threeten.bp.DateTimeUtils
class:
// convert ZonedDateTime to Date
Date date = DateTimeUtils.toDate(z1.toInstant());
// convert back to ZonedDateTime (using Berlin timezone)
ZonedDateTime z = DateTimeUtils.toInstant(date).atZone(ZoneId.of("Europe/Berlin"));
java.time
java.util
日期时间 API 及其格式 API、SimpleDateFormat
已过时且容易出错。建议完全停止使用它们并切换到 modern Date-Time API*.
此外,下面引用的是来自 home page of Joda-Time 的通知:
Note that from Java SE 8 onwards, users are asked to migrate to java.time (JSR-310) - a core part of the JDK which replaces this project.
不要为时区使用固定文本:
不要像您所做的那样为时区使用固定文本(例如 'GMT'
),因为该方法对于其他语言环境可能会失败。
解决方案使用 java.time
,现代日期时间 API:
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
public class Main {
public static void main(String[] args) {
// Test
System.out.println(parse("Tue Jun 20 14:53:08 CEST 2017"));
System.out.println(parse("Tue Jun 20 13:40:37 GMT+02:00 2017"));
}
static ZonedDateTime parse(String strDateTime) {
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("E MMM d H:m:s z u", Locale.ENGLISH);
return ZonedDateTime.parse(strDateTime, dtf);
}
}
输出:
2017-06-20T14:53:08+02:00[Europe/Paris]
2017-06-20T13:40:37+02:00[GMT+02:00]
了解有关现代日期时间 API 的更多信息
* 无论出于何种原因,如果您必须坚持Java 6 或Java 7,您可以使用ThreeTen-Backport which backports most of the java.time functionality to Java 6 & 7. If you are working for an Android project and your Android API level is still not compliant with Java-8, check Java 8+ APIs available through desugaring and