Android上的时间解析问题

时间:2017-07-21 12:31:55

标签: java android time parseexception datetime-parsing

尝试解析时间字符串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;
    }

}

在另一个时间格式为"02:22 p.m."的活动中调用该方法。 olddateformatnewdateformat相同:hh:mm a

它会在日志中导致以下错误:

  

java.text.ParseException:Unparseable date:“02:22 p.m.” (抵消6)

如何解决此问题?时间恰好是上面提到的格式。

2 个答案:

答案 0 :(得分:3)

在上午和下午发生这种情况。在盖尔语言环境中被称为这个。至少在我的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分

正如Hugo如此热烈而正确地推荐his answer,我正在使用现代Java日期和时间API,因此您需要ThreeTenABP才能获得上述代码。见How to use ThreeTenABP in Android Project。或者,您可能希望使用已过时的SimpleDateFormat尝试使用相同的区域设置。

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

答案 1 :(得分:2)

我认为无法自定义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值,您需要所有日期字段(日/月/年)和时区。由于这些字段不在输入String中,SimpleDateFormat将它们设置为 1970年1月1日 st (并且还将秒和毫秒设置为零),并使用系统的默认时区。

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

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

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

Java的新日期/时间API

旧类(DateCalendarSimpleDateFormat)有lots of problemsdesign issues,它们将被新API取代。< / p>

在Android中,您可以使用ThreeTen Backport,这是Java 8新日期/时间类的绝佳后端。您还需要ThreeTenABP(更多关于如何使用它here)。

所有相关课程都在org.threeten.bp包中。

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

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对应的值(并且只有此值,它没有日期字段(日/月/年)或时区)。

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

在旧API中,SimpleDateFormat尝试“智能”并为系统默认设置的那些字段设置默认值( 1970年1月1日 st 时区),但新的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) - 例如,这将获得 5月20日 th ,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类)并始终使用相同的时区。因此,您可以直接将其解析为org.threeten.bp.Instant并获取millis值:

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();