如何将LocalDateTime转换为UTC并返回,而不加载区域?

时间:2018-04-11 09:00:18

标签: android timezone utc threetenbp

背景

我正在使用threetenbp backport for Android(here)来处理各种与时间相关的数据操作。

其中一个是将时间转换为不同的时区(当前为UTC并返回)。

我知道如果你使用类似的东西,这是可能的:

LocalDateTime now = LocalDateTime.now();
LocalDateTime nowInUtc = now.atZone(ZoneId.systemDefault()).withZoneSameInstant(ZoneId.of("UTC")).toLocalDateTime();

这很好用,反之也很容易。

问题

我正在尝试避免库的初始化,这会将相当大的区域文件加载到其中。我已经想出了如何在没有这个的情况下处理各种与日期/时间相关的操作,除了这种转换为UTC并返回的情况。

我得到的错误与正确的转换完全相差1小时。

我尝试了什么

这是我发现并尝试过的:

// getting the current time, using current time zone: 
Calendar cal = Calendar.getInstance();
LocalDateTime now = LocalDateTime.of(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH) + 1, cal.get(Calendar.DAY_OF_MONTH), cal.get(Calendar.HOUR_OF_DAY),
            cal.get(Calendar.MINUTE), cal.get(Calendar.SECOND), cal.get(Calendar.MILLISECOND) * 1000000);

//the conversion itself, which is wrong by 1 hour in my tests: 
LocalDateTime alternativeNowInUtc = now.atZone(ZoneOffset.ofTotalSeconds(TimeZone.getDefault().getRawOffset() / 1000)).withZoneSameInstant(ZoneId.ofOffset("UTC", ZoneOffset.ofHours(0))).toLocalDateTime();

问题

我写的内容究竟出了什么问题?如何在没有初始化库的情况下获得转换时间的替代代码?

如果将LocalDateTime的实例作为输入,如何将其从当前时区转换为UTC,从UTC转换为当前时区?

2 个答案:

答案 0 :(得分:3)

这可能是因为您的JVM的默认时区是夏令时(DST)。

要获得正确的偏移量,您应该检查时区是否在DST中并将其添加到偏移量中:

#include <stdio.h>
#include <stdlib.h>

typedef struct Original_list
{
  int data;
  struct Original_list *next;
}original_list;

original_list *Input();

void EX2()
{
  original_list *list;
  list = Input();

}

original_list *Input()
{
   original_list *lst, *curr_point;
   int c;
   printf("Please enter a value to the first data: \n");
   scanf_s("%d", &c);
   if (c < 0)
      return NULL;
   lst = (original_list*)malloc(sizeof(original_list));
   curr_point = lst;
   lst->data = c;

   while (c >= 0)
   {
     curr_point->next = (original_list*)malloc(sizeof(original_list));
     curr_point = curr_point->next;
     curr_point->data = c;
     printf("please enter number(scan will stop if a negative number is scanned): \n");
     scanf_s("%d", &c);
   }
   curr_point->next = NULL;
   return lst;
}

另一种将Calendar cal = Calendar.getInstance(); TimeZone zone = TimeZone.getDefault(); // if in DST, add the offset, otherwise add zero int dst = zone.inDaylightTime(cal.getTime()) ? zone.getDSTSavings() : 0; int offset = (zone.getRawOffset() + dst) / 1000; LocalDateTime alternativeNowInUtc = now.atZone(ZoneOffset.ofTotalSeconds(offset)) .withZoneSameInstant(ZoneId.ofOffset("UTC", ZoneOffset.ofHours(0))) .toLocalDateTime(); 创建为nowInUtc的方法是从LocalDateTime创建Instant

Calendar

实际上,您根本不需要LocalDateTime nowInUtc = Instant.ofEpochMilli(cal.getTimeInMillis()) .atOffset(ZoneOffset.ofHours(0)).toLocalDateTime(); ,只需使用Calendar即可获得当前时刻:

Instant.now()

或者,甚至更短,直接使用LocalDateTime nowInUtc = Instant.now().atOffset(ZoneOffset.ofHours(0)).toLocalDateTime();

OffsetDateTime

不确定是否有任何加载时区数据,由您来测试。

我认为可以使用常量LocalDateTime nowInUtc = OffsetDateTime.now(ZoneOffset.ofHours(0)).toLocalDateTime(); 代替ZoneOffset.UTC,因为它也不会加载tz数据(但我还没有测试过它) )。

最终解决方案

假设默认时区位于以色列(ZoneOffset.ofHours(0)TimeZone.getDefault()):

Asia/Jerusalem

答案 1 :(得分:0)

carlBjqsd的答案​​还可以,只是尴尬,应该更清楚一点。

为什么一小时差异

查看@carlBjqsd的最终解决方案:它使用表达式

int offset = zone.getOffset(1, now.getYear(), now.getMonthValue() - 1, now.getDayOfMonth(), dayOfWeek, now.getNano() / 1000000);

而不是

getRawOffset()

这导致你观察到一小时的差异。应用程序通常不需要仅使用原始偏移量进行计算,该偏移量在一年中的某些时段省略了dst-offset。 只有在从本地时间戳到UTC并且返回的任何转换中重要的总偏移量。部分偏移(如原始偏移或dst偏移)的细粒度区分的主要目的是正确命名区域(我们称之为标准时间吗?)。

误导性问题标题:&#34;没有加载区域&#34;

,如果要使用区域在本地时间戳和UTC之间进行转换,则永远不能避免加载区域。您真正的问题是:如何避免加载ThreetenABP的区域并改为使用/加载Android平台的区域。您的动机似乎是:

  

我试图避免库的初始化,这会加载很多   大量的区域文件

好吧,我还没有测量哪个区域数据对性能有更大的影响。我只能根据我的研究和所涉及库的源代码的知识来说java.time和ThreetenBP将整个文件TZDB.dat加载到内存中的二进制数组缓存中(作为第一步),然后选择单个区域的相关部分(即通过反序列化将二进制数据数组的一部分解释为一组区域规则,最后是单个ZoneId)。旧Java平台改为使用一组不同的zi文件(每个区域一个),我怀疑Android区域的行为方式类似(但如果您更了解详细信息,请纠正我。)

如果只使用一个区域,那么使用单独区域文件的传统方法可能会更好,但是一旦你想迭代所有可用区域,那么最好只有一个区域文件。

就个人而言,我认为表现方面是可以忽略的。如果你使用Android区域,你也会有一些加载时间,不可避免。 如果你真的想加快ThreetenABP的初始化时间,你应该考虑在后台线程中加载它。

Android区域和ThreetenABP区域是否相同?

一般不是。两个时区存储库可能为混凝土区域提供相同的偏移量。而且他们经常会这样做,但有时候会有不同的差异在你无法控制的范围内。 尽管两个时区存储库最终都使用iana.org/tz的数据,但差异主要是由tzdb数据的不同版本引起的。并且您无法控制Android上存在哪个版本的区域数据平台,因为这取决于手机用户他/她更新Android操作系统的频率。对于ThreetenABP的数据也是如此。您可以提供最新版本的应用程序,包括最新版本的ThreetenABP,但无法控制移动设备用户是否真正更新了应用程序。

为什么要关心选择合适的tz存储库?

除了性能和初始化时间之外,确实有一个特殊的场景可能对选择感兴趣。如果Android操作系统有点陈旧且使用过时版本的区域规则,则某些移动电话用户不会更新其操作系统,而是操纵设备时钟以补偿错误的时区数据。这样,他们仍然可以在手机上获得正确的当地时间(在所有应用中)。

在这种情况下,ThreetenABP不提供良好的解决方案,因为将正确的区域数据与错误的设备时钟相结合将导致错误的本地时间戳(令用户烦恼)。例如土耳其就是一个问题,它在很久以前改变了dst规则。

仅使用Android的旧日历和时区API(在包java.util中)可以考虑问题,从而创建正确的本地时间戳。但是,如果应用程序将UTC时间(例如自1970-01-01T00Z以来的毫秒计数)传送给其他主机(例如服务器),那么错误的设备时钟仍然是一个问题。

我们可以说为什么要费心,因为用户已经完成了“废话”#34;使用设备配置,但​​我们也生活在现实世界中,应该考虑如何使这些用户满意。因此,在考虑解决方案时,我至少在我的日历库Time4A中引入了SystemClock.inPlatformView()方法,这些方法使用(可能)最实际的区域数据,并根据用户的假设获得正确的UTC时钟将至少观察正确的本地设备时间(无论他/她为实现此目标所做的任何事情,通过更新操作系统或通过时钟/区域配置)。我非常高兴以这种方式完全避免使用旧的日历和区域API。我的API甚至允许同时使用两个区域存储库:

  • Timezone.of("java.util.TimeZone~Asia/Jerusalem") //使用Android数据
  • Timezone.of("Asia/Jerusalem") //使用Time4A数据

也许您可以从这些想法中获益,以便为您使用ThreetenABP找到/开发合适的帮助程序类。 Time4A是开源的。