我的xmlGregorianCalendar带有偏移值..怎么在Java中进行调整?

huangapple go评论76阅读模式
英文:

My xmlGregorianCalendar coming with offset value ..how to adjust it? in java

问题

My xmlGregorianCalendar value coming as 2020-10-02T13:07:38-06:00 .. I want to pass this xmlGregorianCalendar value and get the output like 2020-10-02T07:07:38 (which reduces 6 hours from the time) any suggestion on this please?

I have used this below method.

return new Timestamp(xmlGregorianCalendar.toGregorianCalendar(TimeZone.getTimeZone("GMT"),null,null).getTimeInMillis())..

But it removing the off set value but not adjusting the time. the output I am getting is 2020-10-02T13:07:38.. But I am expecting the 2020-10-02T07:07:38 like this.

英文:

My xmlGregorianCalendar value coming as 2020-10-02T13:07:38-06:00 .. I want to pass thisxmlGregorianCalendar value and get the output like 2020-10-02T07:07:38(which reduces 6 hours from the time) any suggestion on this please?

I have used this below method.

return new Timestamp(xmlGregorianCalendar.toGregorianCalendar(TimeZone.getTimeZone("GMT"),null,null).getTimeInMillis())..

But it removing the off set value but not adjusting the time. the output i am getting is 2020-10-02T13:07:38..But i am expecting the 2020-10-02T07:07:38 like this.

答案1

得分: 2

首先,我建议您从过时且容易出错的 java.util 日期时间 API 切换到现代的 java.time 日期时间 API。从 Trail: Date Time 了解更多关于现代日期时间 API 的内容。

你对 Zone-Offset 的理解是不正确的

日期时间字符串 2020-10-02T13:07:38-06:00 告诉我们,给定的日期和时间已经根据偏移量调整为比 UTC 少 6 小时,即相应的 UTC 日期时间将是 2020-10-02T19:07:38Z,其中 Z 指定了 00:00 小时的区偏移量。

这意味着,如果您期望的日期时间是 2020-10-02T07:07:38,您需要进一步将给定的日期时间减去 6 小时,即总共相对于 UTC 的偏移量为 -12:00 小时。

以下示例说明了这个概念:

import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;

public class Main {
    public static void main(String[] args) {
        OffsetDateTime odtGiven = OffsetDateTime.parse("2020-10-02T13:07:38-06:00");
        System.out.println(odtGiven);

        // Date and time at Zone-Offset of 00:00 hours
        OffsetDateTime odtUTC = odtGiven.withOffsetSameInstant(ZoneOffset.UTC);
        System.out.println(odtUTC);

        // Date and time at Zone-Offset of -12:00 hours
        OffsetDateTime odtDerived = odtGiven.withOffsetSameInstant(ZoneOffset.of("-12:00"));
        System.out.println(odtDerived);

        // Get the date-time string in the format with Zone-Offset dropped
        String strDateTimeZoneOffsetDropped = odtDerived.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
        System.out.println(strDateTimeZoneOffsetDropped);
    }
}

输出:

2020-10-02T13:07:38-06:00
2020-10-02T19:07:38Z
2020-10-02T07:07:38-12:00
2020-10-02T07:07:38

使用传统 API:

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;

import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.XMLGregorianCalendar;

public class Main {
    public static void main(String[] args) throws DatatypeConfigurationException, ParseException {
        String givenDateTimeString = "2020-10-02T13:07:38-06:00";
        XMLGregorianCalendar xmlGregorianCalendar = DatatypeFactory.newInstance()
                .newXMLGregorianCalendar(givenDateTimeString);
        System.out.println(xmlGregorianCalendar);

        // Derive the date-time string at Zone-Offset of UTC-12:00 hours
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
        sdf.setTimeZone(TimeZone.getTimeZone("GMT-12:00"));
        Date date = xmlGregorianCalendar.toGregorianCalendar().getTime();
        String derivedDateTimeString = sdf.format(date);
        System.out.println(derivedDateTimeString);
    }
}

输出:

2020-10-02T13:07:38-06:00
2020-10-02T07:07:38
英文:

First of all, I recommend you switch from the outdated and error-prone java.util date-time API to the modern java.time date-time API. Learn more about the modern date-time API from Trail: Date Time.

Your understanding of Zone-Offset is not correct

The date-time string, 2020-10-02T13:07:38-06:00 tells us that the given date and time has been adjusted with an offset of -06:00 hours from UTC i.e. the corresponding date-time at UTC would be 2020-10-02T19:07:38Z where Z specifies a Zone-Offset of 00:00 hours.

It means that if you are expecting a date-time of 2020-10-02T07:07:38, you need to offset the given date-time further by -06:00 hours i.e. it will be at a total of -12:00 hours offset from UTC.

The following example illustrates this concept:

import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;

public class Main {
	public static void main(String[] args) {
		OffsetDateTime odtGiven = OffsetDateTime.parse("2020-10-02T13:07:38-06:00");
		System.out.println(odtGiven);

		// Date and time at Zone-Offset of 00:00 hours
		OffsetDateTime odtUTC = odtGiven.withOffsetSameInstant(ZoneOffset.UTC);
		System.out.println(odtUTC);

		// Date and time at Zone-Offset of -12:00 hours
		OffsetDateTime odtDerived = odtGiven.withOffsetSameInstant(ZoneOffset.of("-12:00"));
		System.out.println(odtDerived);

		// Get the date-time string in the format with Zone-Offset dropped
		String strDateTimeZoneOffsetDropped = odtDerived.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
		System.out.println(strDateTimeZoneOffsetDropped);
	}
}

Output:

2020-10-02T13:07:38-06:00
2020-10-02T19:07:38Z
2020-10-02T07:07:38-12:00
2020-10-02T07:07:38

Using the legacy API:

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;

import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.XMLGregorianCalendar;

public class Main {
	public static void main(String[] args) throws DatatypeConfigurationException, ParseException {
		String givenDateTimeString = "2020-10-02T13:07:38-06:00";
		XMLGregorianCalendar xmlGregorianCalendar = DatatypeFactory.newInstance()
				.newXMLGregorianCalendar(givenDateTimeString);
		System.out.println(xmlGregorianCalendar);

		// Derive the date-time string at Zone-Offset of UTC-12:00 hours
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
		sdf.setTimeZone(TimeZone.getTimeZone("GMT-12:00"));
		Date date = xmlGregorianCalendar.toGregorianCalendar().getTime();
		String derivedDateTimeString = sdf.format(date);
		System.out.println(derivedDateTimeString);
	}
}

Output:

2020-10-02T13:07:38-06:00
2020-10-02T07:07:38

答案2

得分: 2

JDBC 4.2 和 java.time

Arvind Kumar Avinash 已经很好地解释了偏移量的工作原理(XMLGregorianCalendar误导性地称为时区)。他也已经推荐使用现代的 Java 日期和时间 API,即 java.time。我想进一步阐述这个建议。假设您以为您想要一个 java.sql.Timestamp 用于您的 SQL 数据库,那么您不应该想要那个。自从 JDBC 4.2 和 Hibernate 5 开始,我们可以无缝地将 java.time 的类型传输到我们的 SQL 数据库中。我建议您改用这种方式。

如果在 SQL 中使用带有时区的时间戳,请在 Java 中使用带有 UTC 的 OffsetDateTime

如果 SQL 中的数据类型是 timestamp with time zone(建议用于时间戳),从 Java 中传输一个 OffsetDateTime 到它。对于大多数数据库引擎,timestamp with time zone 实际上意味着 UTC 时间戳,所以为了清晰起见,我更喜欢传递一个也在 UTC 中的 OffsetDateTime。有几种转换的方法,每种方法都有其优缺点。

1. 通过 GregorianCalendar 进行转换

// 为了演示,构建与您的等效的 XMLGregorianCalendar
XMLGregorianCalendar xmlGregorianCalendar = DatatypeFactory.newInstance()
        .newXMLGregorianCalendar("2020-10-02T13:07:38-06:00");

// 转换为适用于您的 SQL 数据库的 OffsetDateTime
OffsetDateTime dateTime = xmlGregorianCalendar.toGregorianCalendar()
        .toZonedDateTime()
        .toOffsetDateTime()
        .withOffsetSameInstant(ZoneOffset.UTC);

// 显示结果
System.out.println(dateTime);

输出为:

> 2020-10-02T19:07:38Z

优点:这是官方的转换方法。

或者 2. 通过字符串进行转换

OffsetDateTime dateTime = OffsetDateTime.parse(xmlGregorianCalendar.toString())
        .withOffsetSameInstant(ZoneOffset.UTC);

优点:这很简洁,我认为不会有任何意外。缺点:对我来说,将其格式化为字符串并再次解析为日期时间对象似乎有些浪费。

3. 通过整数进行转换。您可以通过从 XMLGregorianCalendar 中取出各个字段并从中构建一个 OffsetDateTime 进行转换。这变得冗长,处理秒的小数部分有点复杂,并且存在意外交换字段的风险,所以我不建议使用这种方法。

如何传递到 SQL。以下是将 OffsetDateTime 传输到 SQL 的示例。

String sqlString = "insert into your_table(your_timestamp_column) values (?);";
PreparedStatement ps = yourDatabaseConnection.prepareStatement(sqlString);
ps.setObject(1, dateTime);
int rowsInserted = ps.executeUpdate();

如果在 SQL 中使用没有时区的时间戳,请从 Java 中传递 LocalDateTime

如果 SQL 中的数据类型是没有时区的 timestamp(不建议用于时间戳,但经常见到),您需要从 Java 中传递一个 LocalDateTime。同样,有几种转换的方法。我只展示一种:

LocalDateTime dateTime = xmlGregorianCalendar.toGregorianCalendar()
        .toZonedDateTime()
        .withZoneSameInstant(ZoneId.systemDefault())
        .toLocalDateTime();

我使用了 JVM 的默认时区,与旧式的 Timestamp 类所做的方式保持一致,因此结果取决于时区。在我的时区(Europe/Copenhagen),目前的 UTC 偏移为 +02:00,结果是:

> 2020-10-02T21:07:38

在您的代码中出了什么问题?

您之前使用的三参数 XMLGregorianCalendar.toGregorianCalendar​(TimeZone, Locale, XMLGregorianCalendar) 设计上会引入类似您观察到的问题。它承诺使用指定的时区(如果提供了时区)构造一个带有 XMLGregorianCalendar 的字段值(年、月、日、小时、分钟等)的 GregorianCalendar。因此文档已经非常清楚地指出:如果您指定的时区与 XMLGregorianCalendar 的偏移不一致,它将给您提供不同于 XMLGregorianCalendar 指定的时间点。如果 XMLGregorianCalendar 没有偏移,并且我们知道所需的时区,那么这种方法可能偶尔有用。但是您的 XMLGregorianCalendar 已经有偏移,所以这绝对不是您想要使用的方法。

链接

英文:

JDBC 4.2 and java.time

Arvind Kumar Avinash has already nicely explained how the offset works (what XMLGregorianCalendar misleadingly calls a time zone). He has also already recommended java.time, the modern Java date and time API. I should like to elaborate on this recommendation. Assuming that you thought you wanted a java.sql.Timestamp for your SQL database, you should not want that. Since JDBC 4.2 and Hibernate 5 we can seamlessly transfer the types of java.time to our SQL databases. Which I recommend that you do instead.

If using timestamp with time zone in SQL, use OffsetDateTime in UTC in Java

If the datatype in SQL is timestamp with time zone (recommended for timestamps), transfer an OffsetDateTime from Java to it. To most database engines timestamp with time zone really means timestamp in UTC, so for clarity I prefer to pass an OffsetDateTime that is in UTC too. There are a couple of ways to convert, each with their pros and cons.

1. Convert via GregorianCalendar.

	// For demonstration build an XMLGregorianCalenadr equivalent to yours
	XMLGregorianCalendar xmlGregorianCalendar = DatatypeFactory.newInstance()
			.newXMLGregorianCalendar("2020-10-02T13:07:38-06:00");
	
	// Convert to OffsetDateTime for your SQL database
	OffsetDateTime dateTime = xmlGregorianCalendar.toGregorianCalendar()
			.toZonedDateTime()
			.toOffsetDateTime()
			.withOffsetSameInstant(ZoneOffset.UTC);

	// Show what we’ve got
	System.out.println(dateTime);

Output is:

> 2020-10-02T19:07:38Z

Pro: it’s the official conversion.

Or 2. Convert via String.

	OffsetDateTime dateTime = OffsetDateTime.parse(xmlGregorianCalendar.toString())
			.withOffsetSameInstant(ZoneOffset.UTC);

Pro: it’s short, and I don’t think it gives any surprises. Con: To me it feels like a waste to format into a string and parse back into a date-time object again.

3. Convert via int. You may convert by taking the individual fields out of the XMLGregorianCalendar and building an OffsetDateTime from them. It’s getting long-winded, handling fraction of second is a bit complicated, and there’s a risk of accidentally swapping fields, so I don’t recommend it.

How to pass to SQL. Here’s an example of transferring the OffsetDateTime to SQL.

	String sqlString = "insert into your_table(your_timestamp_column) values (?);";
	PreparedStatement ps = yourDatabaseConnection.prepareStatement(sqlString);
	ps.setObject(1, dateTime);
	int rowsInserted = ps.executeUpdate();

If using timestamp without time zone in SQL, instead pass a LocalDateTime from Java

If the datatype in SQL is timestamp without time zone (not recommended for timestamps, but often seen), you need to pass a LocalDateTime from Java. Again there are a couple of ways to convert. I am showing just one:

	LocalDateTime dateTime = xmlGregorianCalendar.toGregorianCalendar()
			.toZonedDateTime()
			.withZoneSameInstant(ZoneId.systemDefault())
			.toLocalDateTime();

I am using the default time zone of the JVM in agreement with what the old-fashioned Timestamp class did, so the result is time zone dependent. In my time zone (Europe/Copenhagen) currently at UTC offset +02:00, the result is:

> 2020-10-02T21:07:38

What went wrong in your code?

The three-arg XMLGregorianCalendar.toGregorianCalendar​(TimeZone, Locale, XMLGregorianCalendar) that you were using is designed to introduce bugs like the one you observed. It promises to construct a GregorianCalendar with the specified time zone (if one is given) and with the field values from the XMLGregorianCalendar (year, month, day, hour, minute, etc.). So the documentation says it quite clearly: if you specify a time zone that does not agree with the offset of the XMLGregorianCalendar, it gives you a different point in time than the one specified by the XMLGregorianCalendar. It might occasionally be useful if the XMLGregorianCalendar hadn’t got any offset and we knew which time zone was intended. But your XMLGregorianCalendar has got offset, so this method definitely is not what you want to use.

huangapple
  • 本文由 发表于 2020年10月3日 06:38:35
  • 转载请务必保留本文链接:https://go.coder-hub.com/64179009.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定