【问题标题】:Format datetime with timezone使用时区格式化日期时间
【发布时间】:2020-12-21 21:29:55
【问题描述】:

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

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

这段代码有什么问题?

【问题讨论】:

  • 我意识到了这个问题。如果我尝试格式化除“yyyy-MM-dd HH:mm:ss”以外的任何模式的日期时间字符串,如果失败。所以我不能用字符串“Mon Dec 21 21:18:37 GMT 2020”来做到这一点,因为这个字符串与模式“yyyy-MM-dd HH:mm:ss”不兼容。因此,如果我从位置获取日期时间,我需要将特殊模式应用于该日期时间字符串
  • @OleV.V.不是重复的。 Your linked Question 询问没有偏移或区域的日期时间。 This Question here询问一个字符串输入,包括GMT作为指示的偏移量。

标签: java java-time datetime-parsing datetimeformatter datetimeparseexception


【解决方案1】:

假设您的数据库有一个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 所在的位置。我不确定,但您似乎一直在混淆输入和输出格式。将日期时间从一种格式的字符串转换为不同格式的字符串涉及两个操作:
    1. 首先,您使用描述原始字符串格式的格式化程序将字符串解析为日期时间对象。
    2. 第二次你格式化你的日期时间成一个字符串,使用一个描述结果字符串格式的格式化程序。
  • 由于原始字符串是英文的,因此在解析时必须使用说英语的语言环境。使用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

【讨论】:

    【解决方案2】:

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

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

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

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

    1. 由于接收到的日期时间在用户指定的时区,所以忽略日期时间字符串中包含的时区并将其解析为LocalDateTime
    2. 在用户指定的时区将LocalDateTime 转换为ZonedDateTime
    3. UTC 将此 ZonedDateTime 转换为 ZonedDateTime
    4. 最后,将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, 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): 
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-08-06
      • 1970-01-01
      • 2011-08-04
      • 2017-08-01
      • 1970-01-01
      相关资源
      最近更新 更多