在将数据转换为设备时区时获得 4 小时的差异?

Getting 4 hours difference while getting while converting data in to device timezone?

我从我设备上的以下代码行中得到 4 小时的时区差异:

我正在以 2018-09-30T13:45:00Z

这样的方式获得时间

我的开始和结束日期如下:-

"start_date":"2017-09-13T12:15:00Z",
"end_date":"2018-09-30T13:45:00Z",

Calendar c = Calendar.getInstance(Locale.getDefault());
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss");
formatter.setTimeZone(TimeZone.getTimeZone("UTC"));
Date localStartDate = formatter.parse(startTime);
Date localEndDate = formatter.parse(endTime);
SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm");

dateFormatter.setTimeZone(c.getTimeZone());
localStartDate = dateFormatter.parse(startTime);
localEndDate = dateFormatter.parse(endTime);
SimpleDateFormat monthFormat = new SimpleDateFormat("MMM");
monthFormat.setTimeZone(TimeZone.getTimeZone(TimeZone.getDefault().getDisplayName()));


String monthName = monthFormat.format(localStartDate);
eventDate.setMonth(monthName);
SimpleDateFormat dateFormat = new SimpleDateFormat("dd");
dateFormat.setTimeZone(TimeZone.getTimeZone(TimeZone.getDefault().getDisplayName()));


String dateName = dateFormat.format(localStartDate);
eventDate.setDate(dateName);
SimpleDateFormat dayNameFormat = new SimpleDateFormat("EEEE");
dayNameFormat.setTimeZone(TimeZone.getTimeZone(TimeZone.getDefault().getDisplayName()));


String dayName1 = dayNameFormat.format(localStartDate);
String dayName2 = dayNameFormat.format(localEndDate);
eventDate.setDayName1(dayName1);
eventDate.setDayName2(dayName2);
SimpleDateFormat timeFormat = new SimpleDateFormat("hh:mm a");
timeFormat.setTimeZone(TimeZone.getTimeZone(TimeZone.getDefault().getDisplayName()));



String startTimeName = timeFormat.format(localStartDate);
String endTimeName = timeFormat.format(localEndDate);


System.out.println("My Start date and end date==>>>"+startTimeName+"  " +endTimeName );

问题:与上面的代码有 4 小时的差异,因为我将时区设置为波士顿(美国),出现错误。

我的结果 来自下面 @Hugo 解决方案如下

期待结果如下

请检查一次..我也设置了东部夏令时的时区但没有得到正确的解决方案..请检查一次..并告诉我

SimpleDateFormatCalendar 使用 JVM 默认时区(除非你在它们上设置了不同的时区),每个 device/machine/environment 中的默认时区可以不同。不仅如此,这个默认值 ,因此最好始终明确说明您使用的是哪个。

当您执行以下操作时:

Calendar c = Calendar.getInstance(Locale.getDefault());
dateFormatter.setTimeZone(c.getTimeZone());
monthFormat.setTimeZone(TimeZone.getTimeZone(TimeZone.getDefault().getDisplayName()));

Calendar 是使用默认时区创建的,因此 dateFormatter 也将具有相同的时区。 monthFormat 以及您创建的其他格式化程序也是如此。唯一设置为不同区域的格式化程序是第一个(设置为 UTC)。

此外,第二个格式化程序是多余的(它与第一个格式化程序所做的相同:将 String 解析为 Date),因此您可以将其删除。

假设您的输入是一个 String,值为 2018-09-30T13:45:00Z:最后的 Z indicates that this date is in UTC。因此,您应该使用设置为 UTC 的格式化程序 解析 它。因此,您不应使用 c.getTimeZone()TimeZone.getDefault(),而应仅使用 TimeZone.getTimeZone("UTC").

对于输出,您必须使用要转换到的时区设置格式化程序。如果时区是 "EDT",请设置为它(但不要完全使用 "EDT",见下文)。如果您想使用 JVM 默认值,请使用 TimeZone.getDefault() - 之前只需检查此值,以确保默认值是您需要的 .

请记住,像 "EDT" 和 "EST" 这样的短名称不是真正的时区。这些缩写是 ambiguous and not standard. Prefer to use IANA timezones names(始终采用 Region/City 格式,例如 America/New_YorkEurope/Berlin)。

因此,当您执行 TimeZone.getTimeZone("EDT")it usually returns "GMT" (because "EDT" is not recognized, and "GMT" is returned as default). That's because "EDT" is used by more than one timezone 时,您必须具体选择您使用的是哪一个(在这些示例中我使用的是 America/New_York)。

另一个细节是,在前 2 个格式化程序中,您使用 hhwhich means "hour of am/pm"(值从 1 到 12),但输入没有 AM/PM 指示符来正确解决这个问题。您需要将其更改为 HH("hour of day",值从 0 到 23)。

    // input is in UTC
TimeZone inputZone = TimeZone.getTimeZone("UTC");
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
formatter.setTimeZone(inputZone);
Date localStartDate = formatter.parse(startTime);
Date localEndDate = formatter.parse(endTime);
...

// removed the second formatter (it was redundant)

// output is in EST (America/New_York)
// or use TimeZone.getDefault() to get JVM default timezone
TimeZone outputZone = TimeZone.getTimeZone("America/New_York");
SimpleDateFormat monthFormat = new SimpleDateFormat("MMM");
monthFormat.setTimeZone(outputZone);
...

SimpleDateFormat dateFormat = new SimpleDateFormat("dd");
dateFormat.setTimeZone(outputZone);
...

SimpleDateFormat dayNameFormat = new SimpleDateFormat("EEEE");
dayNameFormat.setTimeZone(outputZone);
...

SimpleDateFormat timeFormat = new SimpleDateFormat("hh:mm a");
timeFormat.setTimeZone(outputZone);
...

System.out.println("My Start date and end date==>>>" + startTimeName + "  " + endTimeName);

有了这个,你明确地使用 UTC 输入和特定时区输出,而不是依赖 JVM 默认时区(每个设备可能不同,你无法控制)。

输出为:

My Start date and end date==>>>08:15 AM 09:45 AM


Java新Date/TimeAPI

旧的 类(DateCalendarSimpleDateFormat)有 lots of problems and design issues,它们正在被新的 APIs.

在 Android 中你可以使用 ThreeTen Backport, a great backport for Java 8's new date/time classes. To make it work, you'll also need the ThreeTenABP (more on how to use it ).

首先你可以使用org.threeten.bp.Instant来解析输入,因为它是UTC(最后由Z指定)。然后使用 org.threeten.bp.ZoneId 将其转换为 org.threeten.bp.ZonedDateTime:

// output timezone
// or use ZoneId.systemDefault() to get JVM default timezone
ZoneId zone = ZoneId.of("America/New_York");
// parse the inputs
ZonedDateTime startDate = Instant.parse(startTime).atZone(zone);
ZonedDateTime endDate = Instant.parse(endTime).atZone(zone);

然后您可以使用这些对象来获取其他字段:

// get month name
System.out.println(startDate.getMonth().getDisplayName(TextStyle.SHORT, Locale.getDefault()));

这等同于 MMM 模式,它将在默认语言环境中打印月份名称。如果您想要特定语言的月份名称,只需使用另一个 java.util.Locale 值(例如 Locale.ENGLISHjavadoc 中描述的任何其他值)。

org.threeten.bp.format.TextStyle 定义月份名称是窄的(通常只有一个字母)、短的(通常是 2 或 3 个字母)还是完整的(全名)。输出因使用的语言环境而异。

我个人更喜欢不使用默认语言环境,因为即使在运行时也可以在不通知的情况下更改。指定您想要的语言环境总是更好。

要获取日期,您可以选择以 int 或格式化 String(使用 org.threeten.bp.format.DateTimeFormatter)的形式获取:

// get day of month as int
int day = startDate.getDayOfMonth(); // 30
// get day of month as formatted string
String dayStr = DateTimeFormatter.ofPattern("dd").format(startDate); // 30

获取星期几,类似于获取月份的代码:

// get day of week
System.out.println(startDate.getDayOfWeek().getDisplayName(TextStyle.FULL, Locale.getDefault()));

同样的逻辑也适用于此:TextStyle 定义名称的形式(在这种情况下,FULL 等同于 EEEE,并打印全名),语言环境定义了使用的语言。

最后要得到对应的时间,可以再用一个DateTimeFormatter:

// get time
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("hh:mm a");
System.out.println(fmt.format(startDate)); // 08:15 AM
System.out.println(fmt.format(endDate)); // 09:45 AM

这将是您为输出选择的时区中的 date/time。

如果您打算使用 JVM 默认值 (ZoneId.systemDefault()),只需在之前检查它的值以确保它是您想要的值(可能不是因为它可以在运行时更改,所以最好指定一个)。