0

我需要将任何带有用户指定区域设置和时区的传入日期时间字符串解析为唯一模式,以便稍后将其正确存储在数据库中:

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

这段代码有什么问题?

4

2 回答 2

0

正如您已经猜到的那样,错误的根本原因是日期时间字符串的模式与您在DateTimeFormatter. 如果您已经知道获取日期时间字符串的所有日期时间模式,则可以DateTimeFormatter使用多个可选模式创建(通过将模式括在方括号中)。如果您收到未知模式的日期时间(即您没有放入的模式DateTimeFormatter),您可以根据您的要求抛出异常或处理它。

我需要使用用户指定的语言环境和时区将任何传入的日期时间字符串解析为唯一的模式,以便稍后将其正确存储在数据库中:

此要求有两个部分:A.解析并将用户指定的语言环境和时区中的日期时间转换为等效的日期时间UTC(不仅推荐,而且某些数据库也需要,例如PostgreSQLB.将其保存到数据库。

满足第一部分要求的步骤是:

  1. 由于接收到的日期时间在用户指定的时区,因此忽略日期时间字符串中包含的时区并将其解析为LocalDateTime.
  2. 在用户指定的时区转换为LocalDateTimeZonedDateTime
  3. 将其转换ZonedDateTimeUTCZonedDateTime
  4. 最后,转换ZonedDateTimeOffsetDateTime.

一旦你有了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, fr, 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, fr, 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): 
于 2020-12-23T20:31:38.670 回答
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 驱动程序文档应该为您提供更多详细信息。

这段代码有什么问题?

我看到您的代码存在不止一个问题。

  • 正如您在评论中所说的那样,您正在尝试使用带有 pattern 的格式化程序解析字符串yyyy-MM-dd HH:mm:ss,但您的字符串显然不是yyyy-MM-dd HH:mm:ss格式。所以这注定会失败。更具体地说,格式字符串以yyyy年份开头,例如 2020。因此格式化程序希望在字符串的开头找到一个四位数的年份。相反,它会发现Mon并抛出异常。异常消息通知我们字符串could not be parsed at index 0. 索引 0 是字符串的开头,这里Mon是。我不确定,但您似乎一直在混淆输入和输出格式。将日期时间从一种格式的字符串转换为不同格式的字符串涉及两个操作:
    1. 首先,您使用描述原始字符串格式的格式化程序将字符串解析为日期时间对象。
    2. 其次,您使用描述结果字符串格式的格式化程序将日期时间格式化为字符串。
  • 由于原始字符串是英文的,因此在解析时必须使用说英语的语言环境。使用Locale.getDefault()将在讲英语的设备上运行,然后当有一天您在具有不同语言设置的设备上运行它时突然失败。所以这是个坏主意。
  • TemporalAccessor是一个我们应该很少使用的低级接口。而是将您的字符串解析为 a 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

于 2020-12-22T21:20:38.987 回答