【问题标题】:Parsing negative time string with SimpleDateFormat使用 SimpleDateFormat 解析负时间字符串
【发布时间】:2020-12-08 16:14:15
【问题描述】:

在使用 SimpleDateFormat 解析时间字符串时,我看到了一些令我感到困惑的输出。因此,鉴于以下情况:

public class NewMain 
{
    public static void main( String[] args )
    {
        String str1 = "00:00:03";
        parseStr(str1);

        String str2 = "-00:00:03";
        parseStr(str2);

        String str3 = "00:59:59";
        parseStr(str3);

        String str4 = "-00:59:59";
        parseStr(str4);

        String str5 = "01:00:00";
        parseStr(str5);

        String str6 = "-01:00:00";
        parseStr(str6);
    }
    
    private static void parseStr(String aStr)
    {
        SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
        Calendar cal = sdf.getCalendar();
        Date date = cal.getTime();
        System.out.println("SimpleDateFormat.getCalendar.getTime: " + date.toString());
        
        try
        {
            Date dee = sdf.parse(aStr);
            String dStr = dee.toString();
            System.out.println("SimpleDateFormat.parse("+aStr+") = " + dStr);
        }
        catch (ParseException ex)
        {
            ex.printStackTrace();
        }
        System.out.println("");
    }
}

然后输出:

SimpleDateFormat.getCalendar.getTime: Sun Dec 08 10:00:28 CST 1940
SimpleDateFormat.parse(00:00:03) = Thu Jan 01 00:00:03 CST 1970

SimpleDateFormat.getCalendar.getTime: Sun Dec 08 10:00:28 CST 1940
SimpleDateFormat.parse(-00:00:03) = Thu Jan 01 00:00:03 CST 1970

SimpleDateFormat.getCalendar.getTime: Sun Dec 08 10:00:28 CST 1940
SimpleDateFormat.parse(00:59:59) = Thu Jan 01 00:59:59 CST 1970

SimpleDateFormat.getCalendar.getTime: Sun Dec 08 10:00:28 CST 1940
SimpleDateFormat.parse(-00:59:59) = Thu Jan 01 00:59:59 CST 1970

SimpleDateFormat.getCalendar.getTime: Sun Dec 08 10:00:28 CST 1940
SimpleDateFormat.parse(01:00:00) = Thu Jan 01 01:00:00 CST 1970

SimpleDateFormat.getCalendar.getTime: Sun Dec 08 10:00:28 CST 1940
SimpleDateFormat.parse(-01:00:00) = Wed Dec 31 23:00:00 CST 1969

前 2 个负数解析到没有“-”的给定时间。但是,字符串“-01:00:00”解析到完全不同的时间。看起来解析器从 1970 年 1 月 1 日开始减去它。我对此感到很困惑,因为“-00:00:03”和“-00:59:59”没有那种效果!

任何帮助将不胜感激!

【问题讨论】:

  • SimpleDateFormat 用于解析/格式化日期,而不是持续时间或计时器。
  • 更准确地说,@SotiriosDelimanolis,10 年前,SimpleDateFormat 是指一天中的日期和时间。今天它已经不打算再使用了,而且它总是出了名的麻烦。我建议我们使用java.time, the modern Java date and time API

标签: java time simpledateformat java-time datetime-parsing


【解决方案1】:

消极的时间,例如-00:59:59 没有意义。如果您的 API 正在从客户端应用程序获得这样的时间,请丢弃它或教育客户端应用程序的所有者进行更正。另一种选择是在删除负号后解析字符串。

请注意,java.util 的日期时间 API 及其格式化 API SimpleDateFormat 已过时且容易出错。建议完全停止使用,转用modern date-time API

import java.time.LocalTime;

public class Main {
    public static void main(String[] args) {
        // Test
        System.out.println(parseStr("00:00:03"));
        System.out.println(parseStr("00:59:59"));
        System.out.println(parseStr("01:00:00"));
    }

    static LocalTime parseStr(String str) {
        return LocalTime.parse(str);
    }
}

输出:

00:00:03
00:59:59
01:00

通过 Trail: Date Time 了解有关现代日期时间 API 的更多信息。

注意:出于任何原因,如果您必须坚持使用 Java 6 或 Java 7,您可以使用 ThreeTen-Backport,它向后移植了大部分 java.time 功能到 Java 6 和 7。

如果您正在为一个 Android 项目工作,并且您的 Android API 级别仍然不符合 Java-8,请检查 Java 8+ APIs available through desugaringHow to use ThreeTenABP in Android Project

下面是删除负号后如何解析字符串:

import java.time.LocalTime;

public class Main {
    public static void main(String[] args) {
        // Test
        System.out.println(parseStr("00:00:03"));
        System.out.println(parseStr("00:59:59"));
        System.out.println(parseStr("01:00:00"));
        System.out.println(parseStr("-00:00:03"));
        System.out.println(parseStr("-00:59:59"));
        System.out.println(parseStr("-01:00:00"));
    }

    static LocalTime parseStr(String str) {
        if (str.charAt(0) == '-') {
            str = str.substring(1);
        }
        return LocalTime.parse(str);
    }
}

输出:

00:00:03
00:59:59
01:00
00:00:03
00:59:59
01:00

如果您的意思是 Duration(可以是负数)而不是时间,请查看 Duration#toString 以了解持续时间的表示方式。

使用旧版 API:

import java.text.ParseException;
import java.text.SimpleDateFormat;

public class Main {
    public static void main(String[] args) throws ParseException {
        // Test
        System.out.println(parseStr("00:00:03"));
        System.out.println(parseStr("00:59:59"));
        System.out.println(parseStr("01:00:00"));
        System.out.println(parseStr("-00:00:03"));
        System.out.println(parseStr("-00:59:59"));
        System.out.println(parseStr("-01:00:00"));
    }

    static String parseStr(String str) throws ParseException {
        if (str.charAt(0) == '-') {
            str = str.substring(1);
        }
        SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
        return sdf.format(sdf.parse(str));
    }
}

输出:

00:00:03
00:59:59
01:00:00
00:00:03
00:59:59
01:00:00

请注意,日期时间对象不存储格式信息。如果您希望以特定格式打印日期时间对象,您可以使用格式化 API 来实现(例如,现代 API 中的 DateTimeFormatter 和旧 API 中的 SimpleDateFormat)。 LocalTime#toString 返回一个 ISO-8601 格式的字符串,即HH:mm:ss,因此,如果它是预期的格式,则不需要使用DateTimeFormatter

【讨论】:

  • 它不是 API,而是用户输入到字段中。我们的测试员有点邪恶,哈哈,并决定看看如果他们输入负数会发生什么。所以,极不可能,但现在他已经曝光了,我们必须处理它。此外,在可预见的未来,我们将继续使用 Java7。
  • @TracyHiltbrand - 我已经发布了 Java-7 的更新。
  • 谢谢!实际上,我最终通过 contains 方法检查了“-”(我知道我的测试人员大声笑,如果我只检查第一个字符,他会尝试像“--1:00:00 “。感谢您的更新!:-)
  • 我真希望我确切地知道为什么“-”有这种效果...
【解决方案2】:

java.time.Duration

我同意 Sotirios Delimanolis 的假设,即您的字符串代表 时间量,例如持续时间。在许多情况下,这可能是积极的或消极的。如果这个假设是正确的,请在 Java 中使用Duration

Duration 的一个限制是只能将 ISO 8601 格式的字符串直接解析为Duration。所以我使用正则表达式将您的字符串转换为 ISO 8601 格式。

private static void parseStr(String aStr)
{
    // Convert string to ISO 8601 format
    String isoStr = aStr.replaceFirst(
            "^([+-]?)(\\d{2}):(\\d{2}):(\\d{2})$", "$1PT$2H$3M$4S");
    // Parse to Duration
    Duration dur = Duration.parse(isoStr);
    // Print
    System.out.format("Duration.parse(%s) = %s%n", aStr, dur);
    
    System.out.println();
}

使用此方法声明,您的 main 方法将打印:

Duration.parse(00:00:03) = PT3S

Duration.parse(-00:00:03) = PT-3S

Duration.parse(00:59:59) = PT59M59S

Duration.parse(-00:59:59) = PT-59M-59S

Duration.parse(01:00:00) = PT1H

Duration.parse(-01:00:00) = PT-1H

Duration 还打印 ISO 8601 格式。这种格式对许多人来说是不寻常的,但很简单。例如,将PT3S 读取为3 秒的时间段

我通过字符串转换携带了减号,因为它在 ISO 8601 中完全允许。所以我们解析的持续时间都有预期的符号。

Duration 类是现代 Java 日期和时间 API java.time 的一部分。

编辑:如果您愿意,可以将两个 System.out 语句合并为一个:

    System.out.format("Duration.parse(%s) = %s%n%n", aStr, dur);

我只是把一个双 %n 放在那里做两个换行符。

反对意见:我的 Java 7 不能使用 java.time?

此外,在可预见的未来,我们将继续使用 Java7。

java.time 可以很好地与 Java 7 配合使用。它至少需要 Java 6

  • 在 Java 8 及更高版本以及更新的 Android 设备(从 API 级别 26 起)中,现代 API 是内置的。
  • 在(非 Android)Java 6 和 7 中,获取 ThreeTen Backport,这是现代类的后向端口(JSR 310 的 ThreeTen;请参阅底部的链接)。
  • 在较旧的 Android 上,请使用脱糖或 Android 版本的 ThreeTen Backport。它被称为 ThreeTenABP。在后一种情况下,请确保从 org.threeten.bp 导入日期和时间类以及子包。

你必须处理它吗?

我们的测试员有点邪恶,哈哈,决定看看会发生什么 如果他们输入负数。所以,极不可能,但现在他 把它暴露出来,我们必须处理它。

虽然我不了解您的组织,但我来自哪里,可以挑战要求。如果负数时间很有意义并且对您的用户来说是一个有用的概念,请处理它。如果它没有很好的意义,请修改要求以指定不允许任何标志。当您的测试人员(似乎做得很好)无论如何都输入了一个错误消息时,请发出错误消息,而不是试图理解无意义的。

你的代码发生了什么?

我真希望我确切地知道为什么“-”会有这种效果...

首先,SimpleDateFormat(我想这是错误的)将您的字符串视为一天中的时间,而不是持续时间。其次,具有标准设置的SimpleDateFormat 会混淆地接受一天中的负数小时。例如:

  • -00:00:03 表示一天中的小时 = -0,小时的分钟 = 0,分钟的秒 = 3。由于 -0 与 0 相同,因此您将得到 00:00:03。日期默认为 1970 年 1 月 1 日。
  • -01:00:00 中,一天中的小时为–1。 –1 是 0 小时之前的一小时,因此与前一天晚上的 23 相同。日期仍默认为 1970 年 1 月 1 日。因此,您会得到 1969 年 12 月 31 日的 23:00:00。

在所有情况下,只有在我认为您的测试人员打算否定整个持续时间的情况下,才会使用任何减号来否定一天中的小时。

链接

【讨论】:

    猜你喜欢
    • 2013-11-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多