问题描述
我需要将用户指定的语言环境和时区的任何传入日期时间字符串解析为唯一模式,以便稍后将其正确存储在数据库中:
String inputDatetime = "Mon Dec 21 21:18:37 GMT 2020";
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withLocale(Locale.getDefault()).withZone(ZoneOffset.UTC);
TemporalAccessor date = fmt.parse(inputDatetime);
但我收到以下错误:
java.time.format.DateTimeParseException: Text 'Mon Dec 21 21:18:37 GMT 2020' Could not be parsed at index 0
这段代码有什么问题?
解决方法
假设您的数据库有一个 timestamp with time zone
数据类型,您应该使用它来存储字符串中的日期和时间。您的输入字符串明确定义了一个时间点,timestamp with time zone
也是如此。
接下来,您不应将日期时间存储为数据库喜欢的特定格式的字符串。存储适当的日期时间对象。从 JDBC 4.2 开始,这意味着来自 java.time 的类型的对象,这是您已经在使用的现代 Java 日期和时间 API。那么你就不需要关心格式了。这一切都为您服务。如果您的数据库数据类型是 timestamp with time zone
,请将 OffsetDateTime
存储到该列中。相反,它是没有时区或 timestamp
的 datetime
,而是存储一个 LocalDateTime
。您的 JDBC 驱动程序文档应该为您提供更多详细信息。
这段代码有什么问题?
我发现您的代码存在多个问题。
- 正如您在评论中所说的,您正在尝试使用模式为
yyyy-MM-dd HH:mm:ss
的格式化程序解析字符串,但您的字符串显然不是yyyy-MM-dd HH:mm:ss
格式。所以这注定是失败的。更具体地说,格式字符串以yyyy
开头,表示年代,例如 2020。因此格式化程序希望在字符串的开头找到一个四位数字表示年份。相反,它找到Mon
并抛出异常。异常消息通知我们字符串could not be parsed at index 0
。索引 0 是字符串的开头,其中Mon
是。我不确定,但您似乎一直在混淆输入和输出格式。将日期时间从一种格式的字符串转换为另一种格式的字符串涉及两个操作:- 首先,您使用描述原始字符串格式的格式化程序解析您的字符串为日期时间对象。
- 然后,使用描述结果字符串格式的格式化程序将日期时间格式化成一个字符串。
- 由于原始字符串是英文的,所以在解析它时必须使用讲英语的语言环境。使用
Locale.getDefault()
可以在讲英语的设备上运行,但有一天当您在具有不同语言设置的设备上运行它时突然失败。所以这是个坏主意。 -
TemporalAccessor
是一个我们应该很少使用的低级接口。而是将您的字符串解析为ZonedDateTime
,因为它包含日期、时间和时区(在字符串中GMT
算作时区)。
如果您要将日期时间格式化为 DateTimeFormatter
的格式——正如我所说,我认为这不是您应该想要的——以下将起作用:
DateTimeFormatter inputParser = DateTimeFormatter
.ofPattern("EEE MMM dd HH:mm:ss zzz yyyy",Locale.ROOT);
DateTimeFormatter databaseFormatter
= DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss");
String inputDatetime = "Mon Dec 21 21:18:37 GMT 2020";
OffsetDateTime dateTimeToStore = ZonedDateTime.parse(inputDatetime,inputParser)
.toOffsetDateTime()
.withOffsetSameInstant(ZoneOffset.UTC);
String formattedString = dateTimeToStore.format(databaseFormatter);
System.out.println(formattedString);
输出:
2020-12-21 21:18:37
,正如您已经猜到的,错误的根本原因是日期时间字符串中的模式与您在 DateTimeFormatter
中使用的模式不匹配。如果您已经知道获取日期时间字符串的所有日期时间模式,则可以使用多个可选模式创建 DateTimeFormatter
(通过将模式括在方括号中)。如果您收到未知模式的日期时间(即您没有放入 DateTimeFormatter
的模式),您可以抛出异常或根据您的要求处理它。
我需要用用户指定的方式解析任何传入的日期时间字符串 区域设置和时区到唯一模式,以将其正确存储在 稍后数据库:
此要求有两个部分:A. 将用户指定的语言环境和时区中的日期时间解析并转换为 UTC
处的等效日期时间(不仅推荐但也被某些数据库要求,例如 PostgreSQL
) B. 将其保存到数据库中。
满足第一部分要求的步骤是:
- 由于接收到的日期时间在用户指定的时区中,因此忽略日期时间字符串中包含的时区并将其解析为
LocalDateTime
。 - 在用户指定的时区将
LocalDateTime
转换为ZonedDateTime
。 - 在 UTC 将此
ZonedDateTime
转换为ZonedDateTime
。 - 最后,将
ZonedDateTime
转换为OffsetDateTime
。
拥有OffsetDateTime
后,您可以将其存储到数据库中,如下所示:
PreparedStatement st = conn.prepareStatement("INSERT INTO mytable (columnfoo) VALUES (?)");
st.setObject(1,odt);// odt is the instance of OffsetDateTime
st.executeUpdate();
st.close();
您可以使用以下测试工具来测试需求的第一部分:
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.Arrays;
import java.util.Locale;
import java.util.Objects;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
// Test
Scanner scanner = new Scanner(System.in);
while (true) {
System.out.print("Enter the date-time string (press Enter without entering anything to quit): ");
String strDateTime = scanner.nextLine();
if (strDateTime.isBlank()) {
break;
}
boolean valid;
// Create Locale
Locale locale = null;
do {
valid = true;
System.out.print("Enter language code e.g. en,fr,in: ");
String languageTag = scanner.nextLine();
if (!isValidForLocale(languageTag)) {
System.out.println("Invalid code. Please try again.");
valid = false;
} else {
locale = Locale.forLanguageTag(languageTag);
}
} while (!valid);
// Create ZoneId
ZoneId zoneId = null;
do {
valid = true;
System.out.print("Enter timezone in the format Continent/City e.g. Asia/Calcutta: ");
String timezone = scanner.nextLine();
try {
zoneId = ZoneId.of(timezone);
} catch (Exception e) {
System.out.println("Invalid timezone. Please try again.");
valid = false;
}
} while (!valid);
try {
System.out.println(getDateTimeInUTC(strDateTime,locale,zoneId));
} catch (DateTimeParseException e) {
System.out.println("The date-time string has the following problem:\n" + e.getMessage());
System.out.println("Please try again.");
}
}
}
static OffsetDateTime getDateTimeInUTC(String strDateTime,Locale locale,ZoneId zoneId)
throws DateTimeParseException {
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("[uuuu-M-d H:m:s][EEE MMM d H:m:s zzz uuuu]",locale);
// Ignore the timezone contained in strDateTime and parse strDateTime to
// LocalDateTime. Then,convert the LocalDateTime to ZonedDateTime at zoneId.
// Then,convert this ZonedDateTime to ZonedDateTime at UTC. Finally,convert
// the ZonedDateTime to OffsetDateTime and return the same.
ZonedDateTime zdt = LocalDateTime.parse(strDateTime,dtf).atZone(zoneId).withZoneSameInstant(ZoneOffset.UTC);
return zdt.toOffsetDateTime();
}
static boolean isValidForLocale(String languageTag) {
return Arrays.stream(Locale.getISOLanguages()).anyMatch(l -> Objects.equals(l,languageTag));
}
}
示例运行:
Enter the date-time string (press Enter without entering anything to quit): Mon Dec 21 21:18:37 GMT 2020
Enter language code e.g. en,in: en
Enter timezone in the format Continent/City e.g. Asia/Calcutta: Asia/Calcutta
2020-12-21T15:48:37Z
Enter the date-time string (press Enter without entering anything to quit): 2020-1-23 5:15:8
Enter language code e.g. en,in: en
Enter timezone in the format Continent/City e.g. Asia/Calcutta: Asia/Calcutta
2020-01-22T23:45:08Z
Enter the date-time string (press Enter without entering anything to quit):