【问题标题】:Time parsing issue on AndroidAndroid上的时间解析问题
【发布时间】:2017-07-21 12:31:54
【问题描述】:

我在尝试解析时间字符串 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:无法解析的日期:“02:22 p.m.” (偏移量 6)

如何解决这个问题?时间正是上述格式。

【问题讨论】:

  • 那么olddateformatnewdateformat变量的值是多少?
  • 两者相同_"hh:mm a"
  • 我相信SimpleDateFormat不能自定义解析p.m.(它只识别PM)。此外,要获得 nowMilliseconds 值,您需要所有日期字段(日/月/年)和时区。 SimpleDateFormat 会将其设置为 1970 年 1 月 1 日(并将秒数设置为零),并使用系统的默认时区。这就是你想要的吗?
  • 如果时间格式为“02:22 PM”,我可以得到 nowMilliseconds。是否有任何可能的格式来解析 p.m.输入?
  • 如果你去掉点(恐怕这是唯一的方法)。但是您知道nowMillisecondstimestamp 值吗? (自1970-01-01T00:00Z 以来的毫秒数)。该值不仅取决于小时,还取决于日、月、年和时区。 SimpleDateFormat 为其设置默认值(请参阅我之前的评论),但这是您真正需要的吗?

标签: java android time parseexception datetime-parsing


【解决方案1】:

恰好在上午和下午。在盖尔语地区被称为这个。至少在我的 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")

【讨论】:

    【解决方案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 将它们设置为 January 1st of 1970(并将秒和毫秒设置为零),并且使用系统的默认时区。

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

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

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


    Java 的新日期/时间 API

    旧的类(DateCalendarSimpleDateFormat)有 lots of problemsdesign issues,它们正在被新的 API 取代。

    在 Android 中,您可以使用 ThreeTen Backport,这是 Java 8 新日期/时间类的一个很好的反向移植。你还需要ThreeTenABP(更多关于如何使用它here)。

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

    借助这个新 API,您可以使用 org.threeten.bp.format.DateTimeFormatterBuilder 自定义对应于上午/下午的文本(因此无需手动删除点)。每种情况都有特定的类 - 在这种情况下,输入只有时间字段(小时和分钟),所以我将使用 org.threeten.bp.LocalTime 类(它只代表一个时间 - 小时/分钟/秒/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 的值(只有这个值,它没有日期字段(日/月/年)也没有时区)。

    要获取毫秒值 (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) - 例如,这将得到 2017 年 5 月 20 日th。有了这个,您可以将上面的代码设置为您需要的日期和时区。

    请注意,API 使用IANA timezones names(始终采用Region/City 格式,如America/Sao_PauloAsia/Kolkata)。 避免使用三个字母的缩写(如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();
    

    【讨论】:

      【解决方案3】:

      按照我所做的更改对我来说效果很好。

       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.

      感谢您的回复和参考。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-04-22
        • 1970-01-01
        • 2022-07-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-01-22
        • 1970-01-01
        相关资源
        最近更新 更多