Android 上的时间解析问题

Time parsing issue on Android

我在尝试解析时间字符串时遇到解析异常 02:22 p.m.

我有以下转换函数:

public static long convertdatetotimestamp(String datestring, String newdateformat, String olddateformat){
    SimpleDateFormat originalFormat = new SimpleDateFormat(olddateformat,Locale.ROOT);
    SimpleDateFormat targetFormat = new SimpleDateFormat(newdateformat,Locale.ROOT);
    Date date = null;
    try {
        date = originalFormat.parse(datestring);
        String formattedDate = targetFormat.format(date);
        Date parsedDate = targetFormat.parse(formattedDate);
        long nowMilliseconds = parsedDate.getTime();

        return nowMilliseconds;
    } catch (ParseException e) {
        e.printStackTrace();
        return 0;
    }

}

该方法在另一个 activity 中以时间格式 "02:22 p.m." 调用。 olddateformatnewdateformat 是一样的:hh:mm a.

它导致日志中出现以下错误:

java.text.ParseException: Unparseable date: "02:22 p.m." (at offset 6)

如何解决这个问题?时间正是上面提到的格式。

我认为 SimpleDateFormat 无法自定义解析 p.m. 部分(它只能识别 AMPM)。

所以一种替代方法是删除点:

String time = "02:22 p.m.";
SimpleDateFormat format = new SimpleDateFormat("hh:mm a", Locale.ROOT);
date = format.parse(time.replaceAll("\.", ""));

一个细节:要获得 nowMilliseconds 值,您需要所有日期字段 (day/month/year) 和时区。由于这些字段不在输入 String 中,SimpleDateFormat 将它们设置为 January 1st of 1970(以及将秒和毫秒设置为零),并使用系统的默认时区。

我不确定这种获取 1970 年 1 月的行为在所有 Java 版本中是否一致,这是另一个问题,因为您可以根据 environment/device 密码是运行ning。实际上,您可能会得到不同的结果,因为它使用系统的默认时区,并且这可能因环境而异。

如果我在我的机器上 运行 这段代码,它使用我系统的默认时区 (America/Sao_Paulo),结果是 62520000。但是如果我将时区更改为另一个时区(比方说,Asia/Kolkata),结果是 31920000。您必须了解这种变化并检查这是否是您真正需要的。

另一个细节是,如果 olddateformatnewdateformat 相同,则无需创建 2 个不同的格式化程序。


Java的新Date/TimeAPI

旧的 classes(DateCalendarSimpleDateFormat)有 lots of problems and design issues,它们正在被新的 APIs.

在 Android 中您可以使用 ThreeTen Backport, a great backport for Java 8's new date/time classes. You'll also need the ThreeTenABP (more on how to use it ).

所有相关的 class 都在 org.threeten.bp 包中。

有了这个新的 API,您可以使用 org.threeten.bp.format.DateTimeFormatterBuilder 自定义对应于 AM/PM 的文本(因此无需手动删除圆点)。每种情况都有特定的 classes - 在这种情况下,输入只有时间字段(小时和分钟),所以我将使用 org.threeten.bp.LocalTime class (仅代表时间 - hour/minute/second/nanosecond - 没有日期):

String time = "02:22 p.m.";
// map AM and PM values to strings "a.m." and "p.m."
Map<Long, String> map = new HashMap<Long, String>();
map.put(0L, "a.m.");
map.put(1L, "p.m.");
DateTimeFormatter fmt = new DateTimeFormatterBuilder()
    // hour and minute
    .appendPattern("hh:mm ")
    // use custom values for AM/PM
    .appendText(ChronoField.AMPM_OF_DAY, map)
    // create formatter
    .toFormatter(Locale.ROOT);

// parse the time
LocalTime parsedTime = LocalTime.parse(time, fmt);

parsedTime 变量将包含对应于 02:22 PM 的值(只有这个值,它没有日期字段 (day/month/year) 也没有时区)。

要获取毫秒值 (number of milliseconds since 1970-01-01T00:00Z),您还需要一个日期 (day/month/year) 和一个时区。正如我之前所说,这些字段会影响最终值。

在旧的 API 中,SimpleDateFormat 尝试成为 "smart" 并为这些字段设置默认值(January 1st[= 1970 年的 104=] 在系统的默认时区),但新的 API 对此更加严格,您必须明确说明您想要的日期和时区。

在这个例子中,我使用的是 Asia/Kolkata 时区,但您可以根据需要更改它(更多内容见下文):

import org.threeten.bp.LocalDate;
import org.threeten.bp.ZoneId;
import org.threeten.bp.ZonedDateTime;

// timezone for Asia/Kolkata
ZoneId zone = ZoneId.of("Asia/Kolkata");
// current date in Kolkata timezone
LocalDate now = LocalDate.now(zone);
// get the parsed time at the specified date, at the specified zone
ZonedDateTime zdt = parsedTime.atDate(now).atZone(zone);
// get the millis value
long millis = zdt.toInstant().toEpochMilli();

如果你想要一个特定的日期而不是当前日期,你可以使用 LocalDate.of(2017, 5, 20) - 这将得到 May 20th, 2017,例如。有了这个,您可以将上面的代码设置为您需要的日期和时区。

请注意 API 使用 IANA timezones names(始终采用 Region/City 格式,如 America/Sao_PauloAsia/Kolkata)。 避免使用 3 个字母的缩写(如 ISTPST),因为它们是 ambiguous and not standard.

您可以通过调用 ZoneId.getAvailableZoneIds().

获取可用时区列表(并选择最适合您的系统的时区)

如果你想完全模拟 SimpleDateFormat 的作用,你可以使用 LocalDate.of(1970, 1, 1) 并使用 ZoneId.systemDefault() 的默认时区 - 但不推荐这样做,因为系统的默认值可以即使在 运行 时间更改,恕不另行通知。最好明确说明您使用的时区。

或者您可以创建一个始终设置日期默认值(使用 org.threeten.bp.temporal.ChronoField class)并始终使用相同时区的格式化程序。所以可以直接解析成一个org.threeten.bp.Instant,得到毫秒值:

String time = "02:22 p.m.";
ZoneId zone = ZoneId.of("Asia/Kolkata");
DateTimeFormatter fmt2 = new DateTimeFormatterBuilder()
    // hour and minute
    .appendPattern("hh:mm ")
    // use custom values for AM/PM (use the same map from previous example)
    .appendText(ChronoField.AMPM_OF_DAY, map)
    // default value for day: 1
    .parseDefaulting(ChronoField.DAY_OF_MONTH, 1)
    // default value for month: January
    .parseDefaulting(ChronoField.MONTH_OF_YEAR, 1)
    // default value for year: 1970
    .parseDefaulting(ChronoField.YEAR, 1970)
    // create formatter at my specific timezone
    .toFormatter(Locale.ROOT).withZone(zone);
// get the millis value
long millis = Instant.from(fmt2.parse(time)).toEpochMilli();

正好a.m。和 p.m。在盖尔语言环境中就是这样称呼的。至少在我的 Java 8 上。我不确定(所有)Android 手机上是否会出现这种情况,但您可以用它做一些实验。

String datestring = "02:22 p.m.";
Locale parseLocale = Locale.forLanguageTag("ga");
DateTimeFormatter originalFormat = DateTimeFormatter.ofPattern("hh:mm a", parseLocale);
System.out.println(LocalTime.parse(datestring, originalFormat));

这会打印

14:22

雨果如此热情而正确地推荐的是 , I am using the modern Java date and time API, so you will need ThreeTenABP for the above code. See 。或者,您可能想尝试与其他过时的 SimpleDateFormat.

相同的语言环境

美国西班牙语语言环境在我的 Java 8 上显示相同的行为,因此您也可以尝试一下:Locale.forLanguageTag("es-US").

我所做的以下更改对我来说效果很好。

 public static long convertdatetotimestamp(String datestring, String newdateformat, String olddateformat){
        DateFormat originalFormat = new SimpleDateFormat(olddateformat,Locale.ENGLISH);
        DateFormat targetFormat = new
                SimpleDateFormat(newdateformat,Locale.ENGLISH);
        Date date = null;
        try {
            date = originalFormat.parse(datestring.replaceAll("\.", ""));
            String formattedDate = targetFormat.format(date);
            Date parsedDate = targetFormat.parse(formattedDate);
            long nowMilliseconds = parsedDate.getTime();

            return nowMilliseconds;
        } catch (ParseException e) {
            e.printStackTrace();
            return 0;
        }

    }

Locale.ENGLISH 你可以使用你的语言环境,英语解决了我的问题。 Reference.

感谢您的回复和参考。