日历给错了一天

时间:2018-12-11 13:55:45

标签: java android date calendar

我从GPS跟踪器获得一个UTC格式的时间字符串,例如:hhmmss.ssss

我想使用 calendar 将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;
}

2 个答案:

答案 0 :(得分:2)

java.time

您使用的是可怕的旧日期时间类,而这些类早已被行业领先的 java.time 类取代了。

使用UTC将当前时刻捕获为OffsetDateTime对象。

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 <26,请参见 ThreeTenABP 项目。


关于 java.time

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

目前位于Joda-Timemaintenance mode项目建议迁移到java.time类。

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

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

在哪里获取java.time类?

ThreeTen-Extra项目使用其他类扩展了java.time。该项目为将来可能在java.time中添加内容提供了一个试验场。您可能会在这里找到一些有用的类,例如IntervalYearWeekYearQuartermore

答案 1 :(得分: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反向端口和/或ThreeTenABP,请参见下文)从InstantDate而非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,即新类的backport(JSR 310的ThreeTen;请参见底部的链接)。
  • 在(较旧的)Android上,使用Android版本的ThreeTen Backport。叫做ThreeTenABP。并确保您使用子包从org.threeten.bp导入日期和时间类。

链接