使用JodaTime将UTC时间转换为本地时间(毫秒)

3

我正在尝试使用Jodatime显示一定时间范围内的交易记录。

我们的服务器要求起始日期和结束日期均为UTC时间(这可能很明显)。因此,任何涉及到这些日期的业务逻辑都应该使用时区设置为DateTimeZone.UTC的DateTime对象,例如:

mStartDate = DateTime.now(UTC).withTimeAtStartOfDay();

这种方法在显示时间时表现良好,但对于本地(系统默认)时区如何增强它,我不知道该怎么做。理想情况下,我想使用DateUtilsformatDateRange函数,传入两个本地时间戳。但是getMillis()函数似乎没有考虑本地偏移量:

Timestamps returning same milisecond times

我也尝试过这样做:

mTimePeriodTitle.setText(DateUtils.formatDateRange(mContext, f, mStartDate.getMillis(),
    mEndDate.getMillis(), DateUtils.FORMAT_SHOW_TIME,
    TimeZone.getDefault().getID()).toString());

但是这并没有产生任何差异。因此,我的问题是如何获得一个带有2个UTC时间戳的漂亮格式化的本地日期范围?

2个回答

6
如果你的DateTime是以UTC格式存在,而你希望将其转换到其他时区,你可以使用withZone方法进行转换。
对于下面的例子,我的默认时区是America/Sao_Paulo(你可以使用DateTimeZone.getDefault()来检查你的时区):
// create today's date in UTC
DateTime mStartDate = DateTime.now(DateTimeZone.UTC).withTimeAtStartOfDay();
// date/time in UTC
System.out.println(mStartDate); // 2017-06-13T00:00:00.000Z
// date/time in my default timezone (America/Sao_Paulo)
System.out.println(mStartDate.withZone(DateTimeZone.getDefault())); // 2017-06-12T21:00:00.000-03:00

输出结果为:

2017-06-13T00:00:00.000Z
2017-06-12T21:00:00.000-03:00

请注意,withZone 方法将日期和时间正确转换为我的时区(在 America/Sao_Paulo 中当前偏移量为 UTC-03:00),因此已相应地进行了调整。
如果您只想获取时间(小时/分钟/秒),可以使用 toLocalTime() 方法:
System.out.println(mStartDate.withZone(DateTimeZone.getDefault()).toLocalTime()); // 21:00:00.000

输出结果为:

21:00:00.000

如果您需要其他格式(例如,不打印毫秒的3个数字),您可以使用 DateTimeFormatter。好处在于您可以在格式化程序中设置时区,因此无需转换 DateTime
// create formatter for hour/minute/second, set it with my default timezone
DateTimeFormatter fmt = DateTimeFormat.forPattern("HH:mm:ss").withZone(DateTimeZone.getDefault());
System.out.println(fmt.print(mStartDate)); // 21:00:00

输出结果如下:

21:00:00

要获取您的范围,您可以使用上述其中一种方法与您的DateTime(mStartDate和mEndDate)一起使用,并使用DateTimeFormatter将其更改为所需的任何格式。
PS:我认为您在使用getMillis()时忽略了一点,即两个日期时间(在UTC和默认时区中)表示相同的瞬间。 您只是将此瞬间转换为本地时间,但是毫秒数是相同的(请考虑一下,现在,在这个时刻,世界上每个人都处于相同的瞬间(相同的毫秒数),但是他们的本地时间可能因所在地不同而有所不同)。 因此,当将UTC DateTime转换为另一个时区时,我们只是找到对应于相同毫秒数的该区域的本地时间。
您可以使用两个对象上的getMillis()方法进行检查。
System.out.println(mStartDate.getMillis()); // 1497312000000
System.out.println(mStartDate.withZone(DateTimeZone.getDefault()).getMillis()); // 1497312000000

请注意,即使我将对象转换为另一个时区,毫秒数仍然相同(1497312000000)。这是因为两者表示相同的瞬间,我只是将它们移动到另一个本地时间不同的时区。

Java新日期/时间API

Joda-Time已经停止使用并被新API取代,因此我不建议使用它开始新项目。如果您的情况是这样的,可以考虑使用新的日期/时间API,但如果您有一个使用Joda的大型代码库或者现在不想迁移它,则可以忽略余下的答案。
无论如何,即使在joda网站上也指出:“请注意,Joda-Time被认为是一个基本完成的项目。没有计划进行重大增强。如果使用Java SE 8,请迁移到java.time(JSR-310)。”。*
如果您使用的是Java 8,请考虑使用新的java.time API。它比旧API更易于使用,更少出错且容错性更好。我不确定它是否已经适用于所有Android版本(但请参见下面的替代方案)。
如果您使用的是Java <= 7,则可以使用ThreeTen Backport,这是Java 8新日期/时间类的一个很好的后移版本。对于Android,有一种方法可以使用它,即使用ThreeTenABP(有关如何使用它的更多信息,请单击此处)。
下面的代码适用于两者。唯一的区别是包名称(在Java 8中为java.time,在ThreeTen Backport(或Android的ThreeTenABP)中为org.threeten.bp),但类和方法名称相同。
要获取UTC当天开始时的当前日期,您可以执行以下操作:
// UTC's today at start of the day
ZonedDateTime utc = LocalDate.now(ZoneOffset.UTC).atStartOfDay(ZoneOffset.UTC);
System.out.println(utc); // 2017-06-13T00:00Z

首先,我使用 LocalDate.now(ZoneOffset.UTC) 寻找在 UTC 时区下的当前本地日期。如果我只使用 LocalDate.now(),它会获取我的默认时区下的当前日期,这不是我们想要的(根据你所在的地点和默认时区,它可能与 UTC 不同)。
然后我使用 atStartOfDay(ZoneOffset.UTC) 来获取 UTC 下当天的开始时间。我知道两次使用 UTC 听起来有些多余,但是 API 允许我们在此方法中使用任何时区,并且在我看来,这使得我们想要哪个时区变得明确了(如果日期处于具有夏令时更改的时区中,则一天的开始时间可能不是午夜 - 时区参数是为了保证设置正确的值)。
输出结果为:

2017-06-13T00:00Z

要转换为我的默认时区,可以使用 ZoneId.systemDefault(),在我的情况下返回 America/Sao_Paulo。要进行转换并仅获取本地时间部分,只需执行:
System.out.println(utc.withZoneSameInstant(ZoneId.systemDefault()).toLocalTime()); // 21:00

输出结果为:

21:00

如果你想要更改输出格式,你可以使用一个格式化器:
// formatter for localtime (hour/minute/second)
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("HH:mm:ss");
System.out.println(fmt.format(utc.withZoneSameInstant(ZoneId.systemDefault()))); // 21:00:00

The output is:

21:00:00


1
PS非常重要!自纪元以来的毫秒数在所有时区中都是相同的,因为纪元是1970年1月1日UTC,并且不随时区而改变。 - Ole V.V.

1

java.time

Joda-Time项目现已进入维护模式,该项目建议迁移到java.time类。

时区

我们的服务器要求开始日期和结束日期为UTC时间(这可能是显而易见的)。

是的,对于大部分业务逻辑以及日志记录、存储和交换日期时间值,请使用UTC。将UTC视为唯一真实的时间,其他时区只是变体。仅在业务逻辑的特定规则或向用户展示时才需要应用时区。

对于UTC中的值,请使用InstantInstant类表示UTC时间线上的一个时刻,精度为纳秒(最多九个小数位)。

Instant instant = Instant.now() ;  // Capture the current moment in UTC.

为了在特定的时区中查看同一时刻,请分配一个ZoneId以获取ZonedDateTime对象。相同的时刻,时间轴上的相同点,不同的挂钟时间。
ZoneId zTunis = ZoneId.of( "Africa/Tunis" ) ;
ZonedDateTime zdtTunis = instant.atZone( zTunis ) ;

在另一个区域中查看相同的时刻。
ZoneId zAuckland = ZoneId.of( "Pacific/Auckland" ) ;
ZonedDateTime zdtAuckland = instant.atZone( zAuckland );

字符串

我的问题是如何使用2个UTC时间戳获得格式良好的本地日期范围?

在进行如上所示的UTC到时区的调整后,生成表示它们值的字符串。

要生成表示任何这些对象的标准ISO 8601格式的字符串,只需调用toString

String output = instant.toString() ;

2018-01-23T01:23:45.123456Z

String output = zdtAuckland.toString() :

2018-01-23T14:23:45.123456+13:00[Pacific/Auckland]

要生成其他格式的字符串,请定义格式化模式。或者让java.time自动本地化。
要本地化,请指定:
  • FormatStyle以确定字符串应该有多长或缩写。
  • Locale来确定(a)用于翻译日名称、月份名称等的人类语言,以及(b)决定缩写、大写、标点符号、分隔符等问题的文化规范。
例如:
Locale l = Locale.CANADA_FRENCH ; 
DateTimeFormatter f = DateTimeFormatter.ofLocalizedDateTime( FormatStyle.FULL ).withLocale( l );
String output = zdt.format( f );

2018年1月23日,新西兰夏令时下午2:23:45

请注意,时区与Locale无关。一个是为内容服务,另一个是为展示服务。

时间间隔

当表示一对时刻、一对开始-停止时刻时,请使用ThreeTen-Extra库中的Interval类(链接如下)。该类表示一对Instant对象。

它的toString方法生成的字符串符合标准的ISO 8601格式。对于其他格式和对其他时区的调整,请使用上面提到的代码,针对每个Instant应用ZoneId以生成ZonedDateTime。通过getStartgetEnd访问每个Instant

Interval interval = Interval.of( start , stop ) ;

interval.toString():2007-12-03T10:15:30/2007-12-04T10:15:30

关于java.time

java.time框架内置于Java 8及更高版本中。这些类取代了旧的遗留日期时间类,例如java.util.Date, Calendar, & SimpleDateFormat

Joda-Time项目现在处于维护模式,建议迁移到java.time类。

欲了解更多信息,请参见Oracle教程。并搜索Stack Overflow以获取许多示例和说明。规范为JSR 310

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

如何获取java.time类?

ThreeTen-Extra 项目通过添加额外的类来扩展 java.time。该项目是 java.time 可能未来添加内容的试验场。您可能会在此处找到一些有用的类,例如 IntervalYearWeekYearQuarter更多


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