使用所需时区从毫秒显示正确的时间

7

我正在开发一款应用程序,该应用程序从 Google TimeZone API 中获取数据。简单来说,我有地球上所需位置的毫秒时间。

例如: 1504760156000 显示的是伦敦的日期时间,即:

Thu Sep 07 2017 09:55:56

由于我的时区是比UTC快 +05:00,如果我操作 1504760156000 这些毫秒数,将会显示完整的日期时间,如下所示:

Thu Sep 07 2017 09:55:56 GMT+0500 (巴基斯坦标准时间)

但我想显示:

Thu Sep 07 2017 09:55:56 GMT+0100 (英国夏令时)

问题在于:我有正确的伦敦日期和时间,但无法改变时区而不改变时间,因为伦敦的时间是正确的。

更新

得到一些评论后,你没有理解我的例子。

假设我在巴基斯坦,现在时间是下午1点55分,所以我通过我的应用程序向 GOOGLE API 询问此刻伦敦的时间。 Google API 告诉我伦敦时间是 1504760156000 (上午9:55)毫秒。如果我将这些毫秒数转换为 Date 对象,它将像下面这样打印:

Date date =new Date(1504760156000)

2017年9月7日 09:55:56 GMT+0500(巴基斯坦标准时间)

它将根据我的本地时区进行操作,但我想要以下结果

2017年9月7日 09:55:56 GMT+0100(英国夏令时)

更新2

我已经准备好了UTC中以秒为单位的时间戳,因为Google时区API需要以秒为单位的UTC时间戳格式。

"https://maps.googleapis.com/maps/api/timezone/json?location="+Latitude +","+Longitude+"&timestamp="+currentTimeUTCinSeonds+"&key="API KEY"

谷歌API对伦敦的响应如下所示:

{
    "dstOffset" : 3600,   
    "rawOffset" : 0,  
    "status" : "OK",  
    "timeZoneId" : "Europe/London",  
    "timeZoneName" : "British Summer Time"
}

根据文档:
计算本地时间
给定位置的本地时间是时间戳参数和结果中的dstOffset和rawOffset字段之和。
我将结果总结为timestamp+rawoffset+dstoffset*1000='1504760156000'(在我尝试时)。
项目代码
Long ultimateTime=((Long.parseLong(timeObject1.getDstOffset())*1000)+(Long.parseLong(timeObject1.getRawOffset())*1000)+timestamp*1000);
                    timeObject1.setTimestamp(ultimateTime);  //its Sime POJO object to save current time of queried Location 
                    Date date=new Date(ultimateTime);
                    date1.setText("Date Time : "+date);

如我所说,我正在使用本地时区操作结果,因此它在那个时间给了我以下结果:

Thu Sep 07 2017 09:55:56 GMT+0500 (巴基斯坦标准时间)

但是我知道 API 给了我正确的时间。问题是本地与 UTC 的偏移量。 我只想将 GMT+0500 更改为 GMT+0100


但是时间戳1504760156000相当于巴基斯坦的09:55和伦敦的05:55。在伦敦展示为09:55是不正确的。 - user7605325
@ZaidMirza 1504760156000 不是伦敦时间。它是自1970年01月01日00:00Z(协调世界时的1970年1月1日午夜)以来的毫秒数。它是一个时间戳(一个瞬间,时间轴上的一个点),这个值对于世界上的每个人都是相同的。根据您所在的时区,此相同值可以对应于不同的本地日期和时间。请查看此处,注意相同的瞬间如何对应于每个时区中的不同时间。您还可以在 https://currentmillis.com 将其转换为UTC。 - user7605325
@ZaidMirza 再次强调:1504760156000 只是自 1970-01-01T00:00Z 以来的毫秒数,没有其他含义。这个值对应于UTC时间的04:55,在伦敦是05:55,在巴基斯坦是09:55,在东京是13:55等等。如果您包括从API获取此值的代码,我们可能会更清楚地了解发生了什么。 - user7605325
API返回的是1504760156000,还是你通过操作结果得到这个数字的?因为这个时间戳不对应伦敦的09:55。要么API有问题,要么你在误用它。而你创建的日期是java.util.Date类型的吗?也许如果你包含调用API和创建日期的代码,我们可以更好地了解发生了什么情况。 - user7605325
@Hugo 检查更新2 - Zaid Mirza
显示剩余13条评论
1个回答

6
时间戳代表自纪元以来经过的时间的“绝对”值。例如,您的currentTimeUTCinSeconds代表自Unix纪元(即1970-01-01T00:00Z,或 1970年1月1日UTC时区午夜)以来的秒数。 Java API通常使用自纪元以来的毫秒数。
但是概念是相同的 - 这些值是“绝对”的:它们对于世界上的每个人都是相同的,无论他们在哪里。如果世界上不同地区(不同时区)的2个人同时获取当前时间戳,则它们将获得相同的数字。
改变的是,在不同的时区中,这个相同的数字代表不同的本地日期和时间。
例如,您正在使用的时间戳对应于2017年9月7日08:55:56 UTC,其值为1504774556(自纪元以来的秒数)。此相同的数字对应于伦敦的09:55,卡拉奇的13:55,东京的17:55等。更改此数字将更改所有人的本地时间 - 没有必要对其进行操作。
如果想获得表示当前时间的java.util.Date,只需要执行以下操作:
int currentTimeUTCinSeconds = 1504774556;
// cast to long to not lose precision
Date date = new Date((long) currentTimeUTCinSeconds * 1000);

这个日期将保持值1504774556000(自纪元以来的毫秒数)。这个值对应于伦敦的09:55,卡拉奇的13:55和东京的17:55。

但是打印这个日期将把它转换为您的JVM默认时区(这里是关于Date :: toString()方法行为的很好的解释)。当您使用"Date Time : "+date时,它隐式调用toString()方法,结果是将日期转换为您的默认时区。

如果您想要特定格式和特定时区的日期,您需要一个SimpleDateFormat。仅仅打印日期(使用System.out.println或记录日志)不起作用:因为Date没有格式,所以您无法更改日期对象本身的格式。

我还使用了java.util.Locale来指定月份和星期几必须为英文。如果您不指定语言环境,它将使用系统默认值,并且不能保证始终为英语(甚至可以在运行时更改),因此最好始终指定语言环境:

// use the same format, use English for month and day of week
SimpleDateFormat sdf = new SimpleDateFormat("EEE MMM dd yyyy HH:mm:ss 'GMT'Z (zzzz)", Locale.ENGLISH);
// set the timezone I want
sdf.setTimeZone(TimeZone.getTimeZone("Europe/London"));
// format the date
System.out.println(sdf.format(date));

输出结果将是:

2017年9月7日 星期四 上午9:55:56 GMT+0100(英国夏令时)

请注意,我不需要操作时间戳值。 我不使用Google API,但我认为他们的解释过于复杂,上述代码可以以更简单的方式实现相同的结果。
在您的特定情况下,您可以执行以下操作:
date1.setText("Date Time : "+sdf.format(date));

Java新日期/时间API

旧的类(DateCalendarSimpleDateFormat)存在很多问题设计问题,它们正在被新的API所替代。

在Android中,您可以使用ThreeTen Backport,这是Java 8新日期/时间类的一个很好的后移版本。要使其工作,您还需要ThreeTenABP(有关如何使用它的更多信息在此处)。

为了从时间戳获取日期,我使用org.threeten.bp.Instantorg.threeten.bp.ZoneId将其转换为时区,并创建一个org.threeten.bp.ZonedDateTime。然后,我使用org.threeten.bp.format.DateTimeFormatter对其进行格式化:
int currentTimeUTCinSeconds = 1504774556;
// get the date in London from the timestamp
ZonedDateTime z = Instant.ofEpochSecond(currentTimeUTCinSeconds).atZone(ZoneId.of("Europe/London"));
// format it
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("EEE MMM dd yyyy HH:mm:ss 'GMT'XX (zzzz)", Locale.ENGLISH);
System.out.println(fmt.format(z));

输出结果相同:

Thu Sep 07 2017 09:55:56 GMT+0100 (英国夏令时)

在您的情况下,只需执行:
date1.setText("Date Time : "+fmt.format(z));

现在你懂了吧。这只是一个打印问题。如果我不指定Locale.English会怎样?为什么你要指定它? - Zaid Mirza
3
如果你没有指定地区代码,程序将使用JVM的默认设置(例如,在我使用的JVM中,默认设置为葡萄牙语),这将影响月份、星期几甚至时区名称的输出。可以尝试不指定地区代码运行一下程序并查看输出结果。但如果你想使用特定语言输出,最好使用特定的地区代码。如果在我的JVM中使用默认的地区代码,则输出结果将是葡萄牙语:“Qui set 07 2017 09:55:56 GMT+0100 (Fuso horário de verão da Grã-Bretanha)”。 - user7605325
谢谢您,先生。您帮了我很多忙。您让我更清楚地理解了我的概念。 - Zaid Mirza
这真的很有帮助!谢谢! - Andres Gardiol

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接