【问题标题】:Java Calendar/Date: AM and PM not setting correctlyJava 日历/日期:上午和下午未正确设置
【发布时间】:2017-12-18 01:29:30
【问题描述】:

我有一个接受自定义字符串并将其转换为日期的函数。我的目标是存储今天的日期,但使用字符串提供的自定义小时:分钟。

由于某种原因,调试器显示最后切换了 AM/PM(但流程是正确的)。当我传入12:05am 时,Date 对象存储为 PM 值,而如果我传入12:05pm,则 Date 对象存储为 AM 值。应该是相反的。

代码:

public class DateUtils {

    private static final String AM_LOWERCASE = "am";
    private static final String AM_UPPERCASE = "AM";

    public static Date getDateFromTimeString(String timeStr) {

        Calendar calendar = Calendar.getInstance();

        if (StringUtils.hasText(timeStr)) {

            if (timeStr.indexOf(AM_LOWERCASE) != -1 || timeStr.indexOf(AM_UPPERCASE) != -1) {
                calendar.set(Calendar.AM_PM, Calendar.AM);
            } else {
                calendar.set(Calendar.AM_PM, Calendar.PM);
            }

            // Set custom Hours:Minutes on today's date, based on timeStr
            String[] timeStrParts = timeStr.replaceAll("[a-zA-Z]", "").split(":");
            calendar.set(Calendar.HOUR, Integer.valueOf(timeStrParts[0]));
            calendar.set(Calendar.MINUTE, Integer.valueOf(timeStrParts[1]));
            calendar.set(Calendar.SECOND, 0);
            calendar.set(Calendar.MILLISECOND, 0);
        }

        return calendar.getTime();

    }
}

调试器显示:

输入:12:05am -> Sun Dec 17 12:05:00 EST 2017

输入:12:05pm -> Mon Dec 18 00:05:00 EST 2017

应该是相反的。如果我要使用 SimpleDateFormat 写回这些字符串,我会看到输入 1 在下午 12:05 时返回,输入 2 在上午 12:05 时返回。

另外,对于#2,日期不应向前循环一天。在两种情况下,应存储的日期都是今天的日期,即上午 12:05 或下午 12:05。

我错过了什么吗?目标:

12:05am   ->    Sun Dec 17 00:05:00 EST 2017
12:05pm   ->    Sun Dec 17 12:05:00 EST 2017

【问题讨论】:

  • 你为什么要自己解析时间?这很容易搞砸,就像你刚刚做的那样。使用内置解析器。您还应该使用新的 Java 8 Time API,而不是旧的有缺陷的 Date API。 LocalTime.parse("12:05am", new DateTimeFormatterBuilder().parseCaseInsensitive().appendPatt‌​‌​ern("hh:mma").toFo‌​rm‌​atter(Locale.US)‌​)
  • 您仍然使用早已过时的Calendar 类有什么特别的原因吗?今天我们在java.time, the modern Java date and time API 中做得更好。为了您的方便(尤其是那些将维护您的代码的人),我建议您查看comment by @Andreasthe answer by Basil Bourque

标签: java date


【解决方案1】:

java.time

java.util 日期时间 API 及其格式化 API SimpleDateFormat 已过时且容易出错。建议完全停止使用,改用modern Date-Time API*

避免执行String 操作

执行String 操作(例如,正则表达式匹配、转大写、转小写等)可能容易出错且不完整。而是使用DateTimeFormatterBuilder,它允许一组丰富的选项(例如,不区分大小写的解析、指定可选模式、使用默认值解析等)。

演示:

import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.util.Locale;
import java.util.stream.Stream;

public class Main {
    public static void main(String[] args) {
        DateTimeFormatter dtf = new DateTimeFormatterBuilder()
                .parseCaseInsensitive()   //To parse in case-insensitive way e.g. AM, am
                .appendPattern("h:m[ ]a") // Notice single h and m and optional space in square bracket
                .toFormatter(Locale.ENGLISH);
        
        // Test
        Stream.of(
                    "08:20 am",
                    "08:20 pm",
                    "08:20 AM",
                    "08:20 PM",
                    "8:20 am",
                    "08:5 pm",
                    "08:5pm",
                    "8:5am"
        ).forEach(s -> System.out.println(LocalTime.parse(s, dtf)));
    }
}

输出:

08:20
20:20
08:20
20:20
08:20
20:05
20:05
08:05

请注意单个 hm,它们既适用于一位数,也适用于两位数的小时和分钟。另一件需要注意的是使用方括号指定的可选空间。您可以在单个DateTimeFormatter 中指定许多可选模式。检查this answer 展示了DateTimeFormatter 的一些更强大的功能。

需要今天的日期和时区以及时间吗?

使用ZonedDateTime#of(LocalDate, LocalTime, ZoneId) 创建一个。

String strTime = "08:20 am";
ZoneId tz = ZoneId.of("America/Los_Angeles");
System.out.println(ZonedDateTime.of(LocalDate.now(tz), LocalTime.parse(strTime, dtf), tz));

ONLINE DEMO

Trail: Date Time了解更多关于java.timemodern 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

【讨论】:

    【解决方案2】:

    问题在于Calendar.HOUR 的值范围从011,而不是112。当您将小时设置为 12 时,日历会将其标准化为当天的另一半......即您“溢出”到下一天的一半。

    public static void main(String[] args)
    {
        Calendar c1 = Calendar.getInstance();
        System.out.printf("Initial: %s\n",c1.getTime().toString());
        c1.set(Calendar.AM_PM, Calendar.AM);
        System.out.printf("Set(AM): %s\n",c1.getTime().toString());
        c1.set(Calendar.HOUR, 12);
        System.out.printf("Set(12): %s\n\n",c1.getTime().toString());
    
        Calendar c2 = Calendar.getInstance();
        System.out.printf("Initial: %s\n",c2.getTime().toString());
        c2.set(Calendar.AM_PM, Calendar.PM);
        System.out.printf("Set(PM): %s\n",c2.getTime().toString());
        c2.set(Calendar.HOUR, 12);
        System.out.printf("Set(12): %s\n\n",c2.getTime().toString());
    }
    

    输出

    Initial: Sun Dec 17 17:53:52 PST 2017
    Set(AM): Sun Dec 17 05:53:52 PST 2017
    Set(12): Sun Dec 17 12:53:52 PST 2017
    
    Initial: Sun Dec 17 17:53:52 PST 2017
    Set(PM): Sun Dec 17 17:53:52 PST 2017
    Set(12): Mon Dec 18 00:53:52 PST 2017
    

    除此之外,您应该使用自 Java 8 以来已成为 Java 一部分的新 Time 类。它们取代了多年来被称为次优的遗留类(即 Calendar、Date 等) .

    正如@Andreas 所建议的,以下是如何以现代方式解析它:

    LocalTime.parse(
        "12:05am", 
         new DateTimeFormatterBuilder().
             parseCaseInsensitive().
             appendPatt‌​‌​ern("hh:mma").
             toFo‌​rm‌​atter(Locale.US)‌​);
    

    【讨论】:

    • 投了赞成票,但你只是展示出了什么问题,而不是如何解决它,例如LocalTime.parse("12:05am", new DateTimeFormatterBuilder().parseCaseInsensitive().appendPatt‌​ern("hh:mma").toForm‌​atter(Locale.US))
    • 没错,但必须允许 OP some 范围自己学习东西。我已添加您的建议(注明出处,谢谢)
    • 非常感谢 Jim + Andreas!
    【解决方案3】:

    tl;博士

    ZonedDateTime.of(
        LocalDate.now( ZoneId.of( "Africa/Casablanca" )  ) ,
        LocalTime.parse( "12:05am".toUppercase() , DateTimeFormatter.ofPattern( "hh:mma" , Locale.US ) )  ,
        ZoneId.of( "Africa/Casablanca" ) 
    )
    

    java.time

    您正在使用麻烦的旧日期时间类,它们现在是遗留的,被 java.time 类所取代。

    java.time.LocalTime

    您的输入字符串使用不正确的小写“am”/“pm”。这些字母实际上是首字母缩写,因此应该大写。我们必须强制大写以方便解析。

    DateTimeFormatter f = DateTimeFormatter.ofPattern( "hh:mma" , Locale.US ) ;
    LocalTime lt = LocalTime.parse( "12:05am".toUppercase() , f ) ;
    

    您不恰当地尝试使用日期时间类来表示一天中的时间。而是使用 LocalTime 类,因为它适用于没有日期和区域/偏移量的时间。

    今天

    获取当前日期需要时区。

    ZoneId z = ZoneId.of( "Pacific/Auckland" ) ; 
    LocalDate ld = LocalDate.now( z );
    

    合并获得ZonedDateTime

    ZonedDateTime zdt = ZonedDateTime.of( ld , lt , z ) ;
    

    如果由于夏令时等异常情况,该区域中该日期的时间无效,则 java.time 会自动调整。阅读文档以确保您理解并同意调整算法。

    【讨论】:

      猜你喜欢
      • 2011-09-08
      • 1970-01-01
      • 1970-01-01
      • 2021-12-08
      • 2011-06-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多