【问题标题】:Calendar give wrong day日历给出错误的日期
【发布时间】:2019-05-12 12:44:15
【问题描述】:

我从我的 GPS 跟踪器中得到一个 UTC 格式的时间字符串,如下所示:hhmmss.ssss

我想使用 日历 将 UTC 时间转换为用户的本地时间。所以我通过substring(int start, int end)从时间字符串中提取小时、分钟和秒,并通过Calendar.set(int field, int value)函数设置它。在此之后,我将Calendar 转换为Date,但我知道我有错误的一天。

例如timestamp = 091215.0000, 如果我登录Calendar.getInstance()我得到:Thu Dec 11 10:12:15 GMT+01:00 2018 但是当我用我的函数转换它时,我得到:Thu Dec 13 10:12:15 GMT+01:00 2018

我的功能

   public static Date utcToLocalTimeFromLock(String timestamp) {
    Calendar calendar = Calendar.getInstance();

    if (timestamp.charAt(0) == '0') {
        calendar.set(Calendar.HOUR_OF_DAY, timestamp.charAt(1) + 1);
    } else {
        calendar.set(Calendar.HOUR_OF_DAY, Integer.valueOf(timestamp.substring(0, 2) + 1));
    }


    calendar.set(Calendar.MINUTE, Integer.valueOf(timestamp.substring(2, 4)));
    calendar.set(Calendar.SECOND, Integer.valueOf(timestamp.substring(4, 6)));

    Date date = calendar.getTime();
    Log.d(LOG_TAG, "utcToLocalTimeFromLock: " + date);
   return date;
}

【问题讨论】:

  • 我强烈建议您避免自己编写文本处理代码 - Java 中已经有大量代码可以为您执行此操作。接下来,避免使用 java.util.Date 和 java.util.Calendar - 改用 java.time 类型。它们更容易使用。 (例如,您使用 Calendar 意味着它正在解释默认时区中的值,据我所知,这不是您想要实现的......)
  • java.time 是我必须添加到我的 Android 项目中的库吗?我找不到它,我自己写的,因为 GPS 跟踪器的时间不是以秒或毫秒为单位的正常时间戳。我也使用日期,因为这是我将其保存在数据库中的格式。但我看看 java.time :)
  • 它是普通 Java 的一部分。您需要检查哪个版本的 Android 支持它与您定位的 Android 版本。如果你真的非常需要使用Date,我建议你使用SimpleDateFormatter 来进行解析,并确保将时区设置为UTC。
  • 为什么你的时间戳不包含日期?这似乎非常容易出错。您是否使用公共 API 进行 GPS 跟踪?是否有单独的 date 字段可用?
  • 对于 26 以下的 Android API 级别,请使用 java.time 的 backport。它已在ThreeTenABP 中适用于Android。

标签: java android date calendar


【解决方案1】:

java.time

您正在使用多年前被业界领先的 java.time 类取代的糟糕的旧日期时间类。

OffsetDateTime 对象的形式捕获UTC 中的当前时刻。

OffsetDateTime now = OffsetDateTime.now( ZoneOffset.UTC ) ;

将时间解析为LocalTime

String input = "091215.000" ;
DateTimeFormatter f = DateTimeFormatter.ofPattern( "HHmmss.SSS" ) ;
LocalTime lt = LocalTime.parse( input ) ;

应用我们一天中的时间。

OffsetDateTime odt = now.with( lt ) ;

您可能在午夜临近时遇到问题。您可能需要添加一些代码来查看捕获的当前时间是否是新的一天,但您的时间是昨天午夜之前不久。如果是这样,从now 中减去一天。使用对您的情况有意义的任何边界时间值。

if ( 
    lt.isAfter( LocalTime.of( 23 , 55 ) ) 
    && 
    odt.toLocalTime().isBefore( LocalTime.of( 0 , 5 ) ) 
) {
    now = now.minusDays( 1 ) ;
}

从 UTC 调整到您想要的时区。

ZoneId z = ZoneId( "Africa/Tunis" ) ;
ZonedDateTime zdt = odt.atZoneSameInstant( z ) ;

对于 Android ThreeTenABP 项目。


关于java.time

java.time 框架内置于 Java 8 及更高版本中。这些类取代了麻烦的旧 legacy 日期时间类,例如 java.util.DateCalendarSimpleDateFormat

Joda-Time 项目现在位于maintenance mode,建议迁移到java.time 类。

要了解更多信息,请参阅Oracle Tutorial。并在 Stack Overflow 上搜索许多示例和解释。规格为JSR 310

您可以直接与您的数据库交换 java.time 对象。使用符合JDBC 4.2 或更高版本的JDBC driver。不需要字符串,不需要java.sql.* 类。

从哪里获得 java.time 类?

ThreeTen-Extra 项目通过附加类扩展了 java.time。该项目是未来可能添加到 java.time 的试验场。您可以在这里找到一些有用的类,例如IntervalYearWeekYearQuartermore

【讨论】:

  • 感谢您的回答,我明天实施它,看看它如何为我工作 :) 我使用 Date 因为我想保存日期,当我从我的 GPS 跟踪器获取数据时,在我的房间数据库。当我搜索类型转换时,我发现了这个:developer.android.com/training/data-storage/room/…。所以这就是我使用Date的原因。
  • @developKinberg 如果您必须使用 java.util.Date 与尚未更新到 java.time 类的旧代码进行互操作,您可以使用新转换轻松地来回转换添加到旧类的方法。如果使用 ThreeTen-BackportThreeTenABP 项目用于 26 岁之前的旧 Android,请使用 DateTimeUtils 类来回转换。
【解决方案2】:

你的代码出了什么问题?

你的错误在这一行:

        calendar.set(Calendar.HOUR_OF_DAY, timestamp.charAt(1) + 1);

字符在计算机中表示为数字。 Java 的一个令人困惑的事实是,字符和数字在许多情况下可以互换使用。如果timestamp.charAt(1) 是您的示例中的字符'9',它表示为数字57。当您添加1 时,您得到58。当您将一天中的小时设置为58 时——如果您预期Calendar使用默认设置报告错误的类,你错了,这是关于该类的一个令人困惑的事情,这只是我们建议你避免使用它的众多原因之一。它只是不断地数小时到接下来的几天,巧合的是在一天中的正确时间结束,10,仅仅两天后(两天是 48 小时,48 + 10 = 58)。

其他推荐:

  • 不要“手动解析”您的时间字符串。将解析留给库类。
  • 不要通过增加一个小时来转换为中欧时间。这很容易出错。在夏季时间 (DST) 期间,它会给出不正确的结果。它不能移植到其他时区。而是再次将转换留给库类。

如何解决?

Basil Bourque 已经展示了使用 java.time(现代 Java 日期和时间 API)解决问题的好方法。我想向您展示,如果您愿意,您还可以使用 java.time 包含秒的小数部分,而不会有太多麻烦。

static DateTimeFormatter timeFormatter = new DateTimeFormatterBuilder()
        .appendPattern("HHmmss")
        .appendFraction(ChronoField.NANO_OF_SECOND, 3, 4, true)
        .toFormatter(Locale.ROOT);
static ZoneId zone = ZoneId.of("Europe/Paris");

public static ZonedDateTime utcToLocalTimeFromLock(String timestamp) {
    return LocalDate.now(ZoneOffset.UTC)
            .atTime(LocalTime.parse(timestamp, timeFormatter))
            .atOffset(ZoneOffset.UTC)
            .atZoneSameInstant(zone);
}

我们试试吧:

    System.out.println(utcToLocalTimeFromLock(timestamp));

刚刚运行时的输出:

2018-12-11T10:12:15+01:00[欧洲/巴黎]

appendFraction 方法在点后取最小和最大小数位数,因此我分别指定了 3 和 4。根据您的需要,您可以指定最小为 0 位,最大为 9 位数字。

如果欧洲/巴黎没有发生这种情况,当然要替换您自己的时区。

如果您迫切需要一个老式的 Date 对象来存储您现在不想升级的旧版 API:

public static Date utcToLocalTimeFromLock(String timestamp) {
    Instant inst= LocalDate.now(ZoneOffset.UTC)
            .atTime(LocalTime.parse(timestamp, timeFormatter))
            .atOffset(ZoneOffset.UTC)
            .toInstant();
    return Date.from(inst);
}

2018 年 12 月 11 日星期二 10:12:15 CET

如果使用反向端口(ThreeTen Backport 和/或 ThreeTenABP,见下文)从 Instant 转换为 Date 而不是 Date.from(inst),请使用:

    return DateTimeUtils.toDate(inst);

因为Date 没有时区,在这种情况下不需要时区转换。只是因为我恰好也在中欧时区,所以输出是否与您的预期一致——它将是 JVM 时区中的时间。

问题:我可以在 Android 上使用 java.time 吗?

是的,java.time 在较旧和较新的 Android 设备上运行良好。它只需要至少 Java 6

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

链接

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-11-15
    • 1970-01-01
    • 1970-01-01
    • 2017-03-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-01-22
    相关资源
    最近更新 更多