Java转换GMT / UTC到本地时间不能按预期工作

时间:2021-10-11 23:00:49

In Order to show a reproducible scenario, I am doing the following

为了显示可重现的场景,我正在执行以下操作

  1. Get the current system time (local time)

    获取当前系统时间(当地时间)

  2. Convert Local time to UTC // Works Fine Till here

    将本地时间转换为UTC //在这里工作正常

  3. Reverse the UTC time, back to local time. Followed 3 different approaches (listed below) but all the 3 approaches retains the time in UTC only.

    反转UTC时间,返回当地时间。遵循3种不同的方法(如下所列),但所有3种方法仅保留UTC时间。

    {

    long ts = System.currentTimeMillis();
    Date localTime = new Date(ts);
    String format = "yyyy/MM/dd HH:mm:ss";
    SimpleDateFormat sdf = new SimpleDateFormat (format);
    
    // Convert Local Time to UTC (Works Fine) 
    sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
    Date gmtTime = new Date(sdf.format(localTime));
    System.out.println("Local:" + localTime.toString() + "," + localTime.getTime() + " --> UTC time:" + gmtTime.toString() + "-" + gmtTime.getTime());
    
    // Reverse Convert UTC Time to Locale time (Doesn't work) Approach 1
    sdf.setTimeZone(TimeZone.getDefault());        
    localTime = new Date(sdf.format(gmtTime));
    System.out.println("Local:" + localTime.toString() + "," + localTime.getTime() + " --> UTC time:" + gmtTime.toString() + "-" + gmtTime.getTime());
    
    // Reverse Convert UTC Time to Locale time (Doesn't work) Approach 2 using DateFormat
    DateFormat df = new SimpleDateFormat (format);
    df.setTimeZone(TimeZone.getDefault());
    localTime = df.parse((df.format(gmtTime)));
    System.out.println("Local:" + localTime.toString() + "," + localTime.getTime() + " --> UTC time:" + gmtTime.toString() + "-" + gmtTime.getTime());
    
    // Approach 3
    Calendar c = new GregorianCalendar(TimeZone.getDefault());
    c.setTimeInMillis(gmtTime.getTime());
    System.out.println("Local Time " + c.toString());
    

    }

6 个解决方案

#1


56  

I also recommend using Joda as mentioned before.

我也推荐使用前面提到的Joda。

Solving your problem using standard Java Date objects only can be done as follows:

使用标准Java Date对象解决问题的方法如下:

    // **** YOUR CODE **** BEGIN ****
    long ts = System.currentTimeMillis();
    Date localTime = new Date(ts);
    String format = "yyyy/MM/dd HH:mm:ss";
    SimpleDateFormat sdf = new SimpleDateFormat(format);

    // Convert Local Time to UTC (Works Fine)
    sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
    Date gmtTime = new Date(sdf.format(localTime));
    System.out.println("Local:" + localTime.toString() + "," + localTime.getTime() + " --> UTC time:"
            + gmtTime.toString() + "," + gmtTime.getTime());

    // **** YOUR CODE **** END ****

    // Convert UTC to Local Time
    Date fromGmt = new Date(gmtTime.getTime() + TimeZone.getDefault().getOffset(localTime.getTime()));
    System.out.println("UTC time:" + gmtTime.toString() + "," + gmtTime.getTime() + " --> Local:"
            + fromGmt.toString() + "-" + fromGmt.getTime());

Output:

Local:Tue Oct 15 12:19:40 CEST 2013,1381832380522 --> UTC time:Tue Oct 15 10:19:40 CEST 2013,1381825180000
UTC time:Tue Oct 15 10:19:40 CEST 2013,1381825180000 --> Local:Tue Oct 15 12:19:40 CEST 2013-1381832380000

#2


4  

Joda-Time


UPDATE: The Joda-Time project is now in maintenance mode, with the team advising migration to the java.time classes. See Tutorial by Oracle.

更新:Joda-Time项目现在处于维护模式,团队建议迁移到java.time类。请参阅Oracle教程。

See my other Answer using the industry-leading java.time classes.

使用业界领先的java.time类查看我的其他答案。


Normally we consider it bad form on *.com to answer a specific question by suggesting an alternate technology. But in the case of the date, time, and calendar classes bundled with Java 7 and earlier, those classes are so notoriously bad in both design and execution that I am compelled to suggest using a 3rd-party library instead: Joda-Time.

通常我们认为*.com上的错误形式是通过建议替代技术来回答特定问题。但是对于与Java 7及更早版本捆绑在一起的日期,时间和日历类,这些类在设计和执行方面都是如此出名,我不得不建议使用第三方库:Joda-Time。

Joda-Time works by creating immutable objects. So rather than alter the time zone of a DateTime object, we simply instantiate a new DateTime with a different time zone assigned.

Joda-Time通过创建不可变对象来工作。因此,我们只是实例化一个分配了不同时区的新DateTime,而不是改变DateTime对象的时区。

Your central concern of using both local and UTC time is so very simple in Joda-Time, taking just 3 lines of code.

在Joda-Time中,使用本地和UTC时间的中心问题非常简单,仅需3行代码。

    org.joda.time.DateTime now = new org.joda.time.DateTime();
    System.out.println( "Local time in ISO 8601 format: " + now + " in zone: " + now.getZone() );
    System.out.println( "UTC (Zulu) time zone: " + now.toDateTime( org.joda.time.DateTimeZone.UTC ) );

Output when run on the west coast of North America might be:

在北美西海岸运行时的输出可能是:

Local time in ISO 8601 format: 2013-10-15T02:45:30.801-07:00

当地时间以ISO 8601格式:2013-10-15T02:45:30.801-07:00

UTC (Zulu) time zone: 2013-10-15T09:45:30.801Z

UTC(祖鲁语)时区:2013-10-15T09:45:30.801Z

Here is a class with several examples and further comments. Using Joda-Time 2.5.

这是一个有几个例子和进一步评论的课程。使用Joda-Time 2.5。

/**
 * Created by Basil Bourque on 2013-10-15.
 * © Basil Bourque 2013
 * This source code may be used freely forever by anyone taking full responsibility for doing so.
 */
public class TimeExample {
    public static void main(String[] args) {
        // Joda-Time - The popular alternative to Sun/Oracle's notoriously bad date, time, and calendar classes bundled with Java 8 and earlier.
        // http://www.joda.org/joda-time/

        // Joda-Time will become outmoded by the JSR 310 Date and Time API introduced in Java 8.
        // JSR 310 was inspired by Joda-Time but is not directly based on it.
        // http://jcp.org/en/jsr/detail?id=310

        // By default, Joda-Time produces strings in the standard ISO 8601 format.
        // https://en.wikipedia.org/wiki/ISO_8601
        // You may output to strings in other formats.

        // Capture one moment in time, to be used in all the examples to follow.
        org.joda.time.DateTime now = new org.joda.time.DateTime();

        System.out.println( "Local time in ISO 8601 format: " + now + " in zone: " + now.getZone() );
        System.out.println( "UTC (Zulu) time zone: " + now.toDateTime( org.joda.time.DateTimeZone.UTC ) );

        // You may specify a time zone in either of two ways:
        // • Using identifiers bundled with Joda-Time
        // • Using identifiers bundled with Java via its TimeZone class

        // ----|  Joda-Time Zones  |---------------------------------

        // Time zone identifiers defined by Joda-Time…
        System.out.println( "Time zones defined in Joda-Time : " + java.util.Arrays.toString( org.joda.time.DateTimeZone.getAvailableIDs().toArray() ) );

        // Specify a time zone using DateTimeZone objects from Joda-Time.
        // http://joda-time.sourceforge.net/apidocs/org/joda/time/DateTimeZone.html
        org.joda.time.DateTimeZone parisDateTimeZone = org.joda.time.DateTimeZone.forID( "Europe/Paris" );
        System.out.println( "Paris France (Joda-Time zone): " + now.toDateTime( parisDateTimeZone ) );

        // ----|  Java Zones  |---------------------------------

        // Time zone identifiers defined by Java…
        System.out.println( "Time zones defined within Java : " + java.util.Arrays.toString( java.util.TimeZone.getAvailableIDs() ) );

        // Specify a time zone using TimeZone objects built into Java.
        // http://docs.oracle.com/javase/8/docs/api/java/util/TimeZone.html
        java.util.TimeZone parisTimeZone = java.util.TimeZone.getTimeZone( "Europe/Paris" );
        System.out.println( "Paris France (Java zone): " + now.toDateTime(org.joda.time.DateTimeZone.forTimeZone( parisTimeZone ) ) );

    }
}

#3


2  

I am joining the choir recommending that you skip the now long outdated classes Date, Calendar, SimpleDateFormat and friends. In particular I would warn against using the deprecated methods and constructors of the Date class, like the Date(String) constructor you used. They were deprecated because they don’t work reliably across time zones, so don’t use them. And yes, most of the constructors and methods of that class are deprecated.

我正在加入合唱团,建议您跳过现在已久的过时类Date,Calendar,SimpleDateFormat和朋友。特别是我会警告不要使用Date类的弃用方法和构造函数,比如你使用的Date(String)构造函数。它们被弃用,因为它们不能跨时区可靠地工作,所以不要使用它们。是的,该类的大多数构造函数和方法都已弃用。

While at the time you asked the question, Joda-Time was (from all I know) a clearly better alternative, time has moved on again. Today Joda-Time is a largely finished project, and its developers recommend you use java.time, the modern Java date and time API, instead. I will show you how.

当你问这个问题的时候,Joda-Time(据我所知)是一个明显更好的选择,时间又重新开始了。今天Joda-Time是一个基本完成的项目,它的开发人员建议你使用java.time,现代Java日期和时间API。我会告诉你如何。

    ZonedDateTime localTime = ZonedDateTime.now(ZoneId.systemDefault());

    // Convert Local Time to UTC 
    OffsetDateTime gmtTime
            = localTime.toOffsetDateTime().withOffsetSameInstant(ZoneOffset.UTC);
    System.out.println("Local:" + localTime.toString() 
            + " --> UTC time:" + gmtTime.toString());

    // Reverse Convert UTC Time to Local time
    localTime = gmtTime.atZoneSameInstant(ZoneId.systemDefault());
    System.out.println("Local Time " + localTime.toString());

For starters, note that not only is the code only half as long as yours, it is also clearer to read.

对于初学者来说,请注意,代码不仅只是你的代码的一半,读起来也更清晰。

On my computer the code prints:

在我的电脑上打印代码:

Local:2017-09-02T07:25:46.211+02:00[Europe/Berlin] --> UTC time:2017-09-02T05:25:46.211Z
Local Time 2017-09-02T07:25:46.211+02:00[Europe/Berlin]

I left out the milliseconds from the epoch. You can always get them from System.currentTimeMillis(); as in your question, and they are independent of time zone, so I didn’t find them intersting here.

我遗漏了纪元的毫秒数。您始终可以从System.currentTimeMillis()获取它们;在你的问题中,它们与时区无关,所以我没有发现它们在这里。

I hesitatingly kept your variable name localTime. I think it’s a good name. The modern API has a class called LocalTime, so using that name, only not capitalized, for an object that hasn’t got type LocalTime might confuse some (a LocalTime doesn’t hold time zone information, which we need to keep here to be able to make the right conversion; it also only holds the time-of-day, not the date).

我犹豫地保留你的变量名localTime。我认为这是一个好名字。现代API有一个名为LocalTime的类,所以使用该名称,只是没有大写,对于没有类型LocalTime的对象可能会混淆一些(LocalTime不保存时区信息,我们需要保留这里能够进行正确的转换;它也只能保存时间,而不是日期。

Your conversion from local time to UTC was incorrect and impossible

您从本地时间到UTC的转换是不正确的,也是不可能的

The outdated Date class doesn’t hold any time zone information (you may say that internally it always uses UTC), so there is no such thing as converting a Date from one time zone to another. When I just ran your code on my computer, the first line it printed, was:

过时的Date类不包含任何时区信息(您可能会说它在内部始终使用UTC),因此没有将Date从一个时区转换为另一个时区的事情。当我在计算机上运行您的代码时,它打印的第一行是:

Local:Sat Sep 02 07:25:45 CEST 2017,1504329945967 --> UTC time:Sat Sep 02 05:25:45 CEST 2017-1504322745000

07:25:45 CEST is correct, of course. The correct UTC time would have been 05:25:45 UTC, but it says CEST again, which is incorrect.

07:25:45当然,CEST是正确的。正确的UTC时间是05:25:45 UTC,但它再次说CEST,这是不正确的。

Now you will never need the Date class again, :-) but if you were ever going to, the must-read would be All about java.util.Date on Jon Skeet’s coding blog.

现在你再也不需要Date类了:-)但是如果你要去,必须阅读的是关于Jon Skeet编码博客上的java.util.Date的全部内容。

Question: Can I use the modern API with my Java version?

问题:我可以在Java版本中使用现代API吗?

If using at least Java 6, you can.

如果至少使用Java 6,则可以。

  • In Java 8 and later the new API comes built-in.
  • 在Java 8及更高版本中,新的API内置。

  • In Java 6 and 7 get the ThreeTen Backport, the backport of the new classes (that’s ThreeTen for JSR-310, where the modern API was first defined).
  • 在Java 6和7中获取ThreeTen Backport,这是新类的后端(这是JSR-310的ThreeTen,其中首先定义了现代API)。

  • On Android, use the Android edition of ThreeTen Backport. It’s called ThreeTenABP, and I think that there’s a wonderful explanation in this question: How to use ThreeTenABP in Android Project.
  • 在Android上,使用Android版的ThreeTen Backport。它被称为ThreeTenABP,我认为这个问题有一个很好的解释:如何在Android项目中使用ThreeTenABP。

#4


1  

I strongly recommend using Joda Time http://joda-time.sourceforge.net/faq.html

我强烈建议使用Joda Time http://joda-time.sourceforge.net/faq.html

#5


1  

You have a date with a known timezone (Here Europe/Madrid), and a target timezone (UTC)

您有一个已知时区的日期(此处为欧洲/马德里)和目标时区(UTC)

You just need two SimpleDateFormats:

你只需要两个SimpleDateFormats:

        long ts = System.currentTimeMillis();
        Date localTime = new Date(ts);

        SimpleDateFormat sdfLocal = new SimpleDateFormat ("yyyy/MM/dd HH:mm:ss");
        sdfLocal.setTimeZone(TimeZone.getTimeZone("Europe/Madrid"));

        SimpleDateFormat sdfUTC = new SimpleDateFormat ("yyyy/MM/dd HH:mm:ss");
        sdfUTC.setTimeZone(TimeZone.getTimeZone("UTC"));

        // Convert Local Time to UTC
        Date utcTime = sdfLocal.parse(sdfUTC.format(localTime));
        System.out.println("Local:" + localTime.toString() + "," + localTime.getTime() + " --> UTC time:" + utcTime.toString() + "-" + utcTime.getTime());

        // Reverse Convert UTC Time to Locale time
        localTime = sdfUTC.parse(sdfLocal.format(utcTime));
        System.out.println("UTC:" + utcTime.toString() + "," + utcTime.getTime() + " --> Local time:" + localTime.toString() + "-" + localTime.getTime());

So after see it working you can add this method to your utils:

所以看到它工作后你可以将这个方法添加到你的utils:

    public Date convertDate(Date dateFrom, String fromTimeZone, String toTimeZone) throws ParseException {
        String pattern = "yyyy/MM/dd HH:mm:ss";
        SimpleDateFormat sdfFrom = new SimpleDateFormat (pattern);
        sdfFrom.setTimeZone(TimeZone.getTimeZone(fromTimeZone));

        SimpleDateFormat sdfTo = new SimpleDateFormat (pattern);
        sdfTo.setTimeZone(TimeZone.getTimeZone(toTimeZone));

        Date dateTo = sdfFrom.parse(sdfTo.format(dateFrom));
        return dateTo;
    }

#6


0  

tl;dr

Instant.now()                           // Capture the current moment in UTC.
.atZone( ZoneId.systemDefault() )       // Adjust into the JVM's current default time zone. Same moment, different wall-clock time. Produces a `ZonedDateTime` object.
.toInstant()                            // Extract a `Instant` (always in UTC) object from the `ZonedDateTime` object.
.atZone( ZoneId.of( "Europe/Paris" ) )  // Adjust the `Instant` into a specific time zone. Renders a `ZonedDateTime` object. Same moment, different wall-clock time.
.toInstant()                            // And back to UTC again.

java.time

The modern approach uses the java.time classes that supplanted the troublesome old legacy date-time classes (Date, Calendar, etc.).

现代方法使用java.time类来取代麻烦的旧遗留日期时间类(日期,日历等)。

Your use of the word "local" contradicts the usage in the java.time class. In java.time, "local" means any locality or all localities, but not any one particular locality. The java.time classes with names starting with "Local…" all lack any concept of time zone or offset-from-UTC. So they do not represent a specific moment, they are not a point on the timeline, whereas your Question is all about moments, points on the timeline viewed through various wall-clock times.

您对“local”一词的使用与java.time类中的用法相矛盾。在java.time中,“local”表示任何地点或所有地点,但不是任何一个特定地点。名称以“Local ...”开头的java.time类都缺少时区或从UTC偏移的概念。因此,它们不代表特定的时刻,它们不是时间轴上的一个点,而您的问题是关于时刻,通过各种挂钟时间查看时间轴上的点。

Get the current system time (local time)

获取当前系统时间(当地时间)

If you want to capture the current moment in UTC, use Instant. The Instant class represents a moment on the timeline in UTC with a resolution of nanoseconds (up to nine (9) digits of a decimal fraction).

如果要捕获UTC中的当前时刻,请使用“即时”。 Instant类表示UTC时间轴上的一个时刻,分辨率为纳秒(最多九(9)位小数)。

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

Adjust into a time zone by applying a ZoneId to get a ZonedDateTime. Same moment, same point on the timeline, different wall-clock time.

通过应用ZoneId调整到时区以获取ZonedDateTime。同一时刻,时间轴上的同一点,不同的挂钟时间。

Specify a proper time zone name in the format of continent/region, such as America/Montreal, Africa/Casablanca, or Pacific/Auckland. Never use the 3-4 letter abbreviation such as EST or IST as they are not true time zones, not standardized, and not even unique(!).

以洲/地区的格式指定适当的时区名称,例如America / Montreal,Africa / Casablanca或Pacific / Auckland。切勿使用3-4字母缩写,例如EST或IST,因为它们不是真正的时区,不是标准化的,甚至不是唯一的(!)。

ZoneId z = ZoneId.of( "America/Montreal" ) ;
ZonedDateTime zdt = instant.atZone( z ) ;  // Same moment, different wall-clock time.

As a shortcut, you can skip the usage of Instant to get a ZonedDateTime.

作为快捷方式,您可以跳过使用Instant来获取ZonedDateTime。

ZoneId z = ZoneId.of( "America/Montreal" ) ;
ZonedDateTime zdt = ZonedDateTime.now( z ) ;

Convert Local time to UTC // Works Fine Till here

将本地时间转换为UTC //在这里工作正常

You can adjust from the zoned date-time to UTC by extracting an Instant from a ZonedDateTime.

您可以通过从ZonedDateTime中提取Instant来从分区日期时间调整为UTC。

ZoneId z = ZoneId.of( "America/Montreal" ) ;
ZonedDateTime zdt = ZonedDateTime.now( z ) ;
Instant instant = zdt.toInstant() ;

Reverse the UTC time, back to local time.

反转UTC时间,返回当地时间。

As shown above, apply a ZoneId to adjust the same moment into another wall-clock time used by the people of a certain region (a time zone).

如上所示,应用ZoneId将同一时刻调整为某个区域(时区)的人使用的另一个挂钟时间。

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

ZoneId zDefault = ZoneId.systemDefault() ;  // The JVM's current default time zone.
ZonedDateTime zdtDefault = instant.atZone( zDefault ) ;

ZoneId zTunis = ZoneId.of( "Africa/Tunis" ) ;  // The JVM's current default time zone.
ZonedDateTime zdtTunis = instant.atZone( zTunis ) ;

ZoneId zAuckland = ZoneId.of( "Pacific/Auckland" ) ;  // The JVM's current default time zone.
ZonedDateTime zdtAuckland = instant.atZone( zAuckland ) ;

Going back to UTC from a zoned date-time, call ZonedDateTime::toInstant. Think of it conceptually as: ZonedDateTime = Instant + ZoneId.

从分区日期时间返回UTC,调用ZonedDateTime :: toInstant。从概念上考虑它:ZonedDateTime = Instant + ZoneId。

Instant instant = zdtAuckland.toInstant() ;

All of these objects, the Instant and the three ZonedDateTime objects all represent the very same simultaneous moment, the same point in history.

所有这些对象,Instant和三个ZonedDateTime对象都代表同一时刻,即历史中的同一时刻。

Followed 3 different approaches (listed below) but all the 3 approaches retains the time in UTC only.

遵循3种不同的方法(如下所列),但所有3种方法仅保留UTC时间。

Forget about trying to fix code using those awful Date, Calendar, and GregorianCalendar classes. They are a wretched mess of bad design and flaws. You need never touch them again. If you must interface with old code not yet updated to java.time, you can convert back-and-forth via new conversion methods added to the old classes.

忘记尝试使用那些可怕的Date,Calendar和GregorianCalendar类来修复代码。他们是一个糟糕的设计和缺陷混乱。你不需要再次触摸它们。如果必须与尚未更新到java.time的旧代码接口,则可以通过添加到旧类的新转换方法来回转换。


About java.time

The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.

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

The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.

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

To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.

要了解更多信息,请参阅Oracle教程。并搜索Stack Overflow以获取许多示例和解释。规范是JSR 310。

You may exchange java.time objects directly with your database. Use a JDBC driver compliant with JDBC 4.2 or later. No need for strings, no need for java.sql.* classes.

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

Where to obtain the java.time classes?

从哪里获取java.time类?

  • Java SE 8, Java SE 9, Java SE 10, and later
    • Built-in.
    • Part of the standard Java API with a bundled implementation.
    • 带有捆绑实现的标准Java API的一部分。

    • Java 9 adds some minor features and fixes.
    • Java 9增加了一些小功能和修复。

  • Java SE 8,Java SE 9,Java SE 10和更高版本内置。带有捆绑实现的标准Java API的一部分。 Java 9增加了一些小功能和修复。

  • Java SE 6 and Java SE 7
    • Much of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport.
    • 许多java.time功能都被反向移植到ThreeTen-Backport中的Java 6和7。

  • Java SE 6和Java SE 7许多java.time功能都被反向移植到ThreeTen-Backport中的Java 6和7。

  • Android
    • Later versions of Android bundle implementations of the java.time classes.
    • 更高版本的Android捆绑java.time类的实现。

    • For earlier Android (<26), the ThreeTenABP project adapts ThreeTen-Backport (mentioned above). See How to use ThreeTenABP….
    • 对于早期的Android(<26),ThreeTenABP项目采用ThreeTen-Backport(如上所述)。请参见如何使用ThreeTenABP ....

  • Android更新版本的Android捆绑java.time类的实现。对于早期的Android(<26),ThreeTenABP项目采用ThreeTen-Backport(如上所述)。请参见如何使用ThreeTenABP ....

The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.

ThreeTen-Extra项目使用其他类扩展了java.time。该项目是未来可能添加到java.time的试验场。您可以在这里找到一些有用的课程,如Interval,YearWeek,YearQuarter等。

#1


56  

I also recommend using Joda as mentioned before.

我也推荐使用前面提到的Joda。

Solving your problem using standard Java Date objects only can be done as follows:

使用标准Java Date对象解决问题的方法如下:

    // **** YOUR CODE **** BEGIN ****
    long ts = System.currentTimeMillis();
    Date localTime = new Date(ts);
    String format = "yyyy/MM/dd HH:mm:ss";
    SimpleDateFormat sdf = new SimpleDateFormat(format);

    // Convert Local Time to UTC (Works Fine)
    sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
    Date gmtTime = new Date(sdf.format(localTime));
    System.out.println("Local:" + localTime.toString() + "," + localTime.getTime() + " --> UTC time:"
            + gmtTime.toString() + "," + gmtTime.getTime());

    // **** YOUR CODE **** END ****

    // Convert UTC to Local Time
    Date fromGmt = new Date(gmtTime.getTime() + TimeZone.getDefault().getOffset(localTime.getTime()));
    System.out.println("UTC time:" + gmtTime.toString() + "," + gmtTime.getTime() + " --> Local:"
            + fromGmt.toString() + "-" + fromGmt.getTime());

Output:

Local:Tue Oct 15 12:19:40 CEST 2013,1381832380522 --> UTC time:Tue Oct 15 10:19:40 CEST 2013,1381825180000
UTC time:Tue Oct 15 10:19:40 CEST 2013,1381825180000 --> Local:Tue Oct 15 12:19:40 CEST 2013-1381832380000

#2


4  

Joda-Time


UPDATE: The Joda-Time project is now in maintenance mode, with the team advising migration to the java.time classes. See Tutorial by Oracle.

更新:Joda-Time项目现在处于维护模式,团队建议迁移到java.time类。请参阅Oracle教程。

See my other Answer using the industry-leading java.time classes.

使用业界领先的java.time类查看我的其他答案。


Normally we consider it bad form on *.com to answer a specific question by suggesting an alternate technology. But in the case of the date, time, and calendar classes bundled with Java 7 and earlier, those classes are so notoriously bad in both design and execution that I am compelled to suggest using a 3rd-party library instead: Joda-Time.

通常我们认为*.com上的错误形式是通过建议替代技术来回答特定问题。但是对于与Java 7及更早版本捆绑在一起的日期,时间和日历类,这些类在设计和执行方面都是如此出名,我不得不建议使用第三方库:Joda-Time。

Joda-Time works by creating immutable objects. So rather than alter the time zone of a DateTime object, we simply instantiate a new DateTime with a different time zone assigned.

Joda-Time通过创建不可变对象来工作。因此,我们只是实例化一个分配了不同时区的新DateTime,而不是改变DateTime对象的时区。

Your central concern of using both local and UTC time is so very simple in Joda-Time, taking just 3 lines of code.

在Joda-Time中,使用本地和UTC时间的中心问题非常简单,仅需3行代码。

    org.joda.time.DateTime now = new org.joda.time.DateTime();
    System.out.println( "Local time in ISO 8601 format: " + now + " in zone: " + now.getZone() );
    System.out.println( "UTC (Zulu) time zone: " + now.toDateTime( org.joda.time.DateTimeZone.UTC ) );

Output when run on the west coast of North America might be:

在北美西海岸运行时的输出可能是:

Local time in ISO 8601 format: 2013-10-15T02:45:30.801-07:00

当地时间以ISO 8601格式:2013-10-15T02:45:30.801-07:00

UTC (Zulu) time zone: 2013-10-15T09:45:30.801Z

UTC(祖鲁语)时区:2013-10-15T09:45:30.801Z

Here is a class with several examples and further comments. Using Joda-Time 2.5.

这是一个有几个例子和进一步评论的课程。使用Joda-Time 2.5。

/**
 * Created by Basil Bourque on 2013-10-15.
 * © Basil Bourque 2013
 * This source code may be used freely forever by anyone taking full responsibility for doing so.
 */
public class TimeExample {
    public static void main(String[] args) {
        // Joda-Time - The popular alternative to Sun/Oracle's notoriously bad date, time, and calendar classes bundled with Java 8 and earlier.
        // http://www.joda.org/joda-time/

        // Joda-Time will become outmoded by the JSR 310 Date and Time API introduced in Java 8.
        // JSR 310 was inspired by Joda-Time but is not directly based on it.
        // http://jcp.org/en/jsr/detail?id=310

        // By default, Joda-Time produces strings in the standard ISO 8601 format.
        // https://en.wikipedia.org/wiki/ISO_8601
        // You may output to strings in other formats.

        // Capture one moment in time, to be used in all the examples to follow.
        org.joda.time.DateTime now = new org.joda.time.DateTime();

        System.out.println( "Local time in ISO 8601 format: " + now + " in zone: " + now.getZone() );
        System.out.println( "UTC (Zulu) time zone: " + now.toDateTime( org.joda.time.DateTimeZone.UTC ) );

        // You may specify a time zone in either of two ways:
        // • Using identifiers bundled with Joda-Time
        // • Using identifiers bundled with Java via its TimeZone class

        // ----|  Joda-Time Zones  |---------------------------------

        // Time zone identifiers defined by Joda-Time…
        System.out.println( "Time zones defined in Joda-Time : " + java.util.Arrays.toString( org.joda.time.DateTimeZone.getAvailableIDs().toArray() ) );

        // Specify a time zone using DateTimeZone objects from Joda-Time.
        // http://joda-time.sourceforge.net/apidocs/org/joda/time/DateTimeZone.html
        org.joda.time.DateTimeZone parisDateTimeZone = org.joda.time.DateTimeZone.forID( "Europe/Paris" );
        System.out.println( "Paris France (Joda-Time zone): " + now.toDateTime( parisDateTimeZone ) );

        // ----|  Java Zones  |---------------------------------

        // Time zone identifiers defined by Java…
        System.out.println( "Time zones defined within Java : " + java.util.Arrays.toString( java.util.TimeZone.getAvailableIDs() ) );

        // Specify a time zone using TimeZone objects built into Java.
        // http://docs.oracle.com/javase/8/docs/api/java/util/TimeZone.html
        java.util.TimeZone parisTimeZone = java.util.TimeZone.getTimeZone( "Europe/Paris" );
        System.out.println( "Paris France (Java zone): " + now.toDateTime(org.joda.time.DateTimeZone.forTimeZone( parisTimeZone ) ) );

    }
}

#3


2  

I am joining the choir recommending that you skip the now long outdated classes Date, Calendar, SimpleDateFormat and friends. In particular I would warn against using the deprecated methods and constructors of the Date class, like the Date(String) constructor you used. They were deprecated because they don’t work reliably across time zones, so don’t use them. And yes, most of the constructors and methods of that class are deprecated.

我正在加入合唱团,建议您跳过现在已久的过时类Date,Calendar,SimpleDateFormat和朋友。特别是我会警告不要使用Date类的弃用方法和构造函数,比如你使用的Date(String)构造函数。它们被弃用,因为它们不能跨时区可靠地工作,所以不要使用它们。是的,该类的大多数构造函数和方法都已弃用。

While at the time you asked the question, Joda-Time was (from all I know) a clearly better alternative, time has moved on again. Today Joda-Time is a largely finished project, and its developers recommend you use java.time, the modern Java date and time API, instead. I will show you how.

当你问这个问题的时候,Joda-Time(据我所知)是一个明显更好的选择,时间又重新开始了。今天Joda-Time是一个基本完成的项目,它的开发人员建议你使用java.time,现代Java日期和时间API。我会告诉你如何。

    ZonedDateTime localTime = ZonedDateTime.now(ZoneId.systemDefault());

    // Convert Local Time to UTC 
    OffsetDateTime gmtTime
            = localTime.toOffsetDateTime().withOffsetSameInstant(ZoneOffset.UTC);
    System.out.println("Local:" + localTime.toString() 
            + " --> UTC time:" + gmtTime.toString());

    // Reverse Convert UTC Time to Local time
    localTime = gmtTime.atZoneSameInstant(ZoneId.systemDefault());
    System.out.println("Local Time " + localTime.toString());

For starters, note that not only is the code only half as long as yours, it is also clearer to read.

对于初学者来说,请注意,代码不仅只是你的代码的一半,读起来也更清晰。

On my computer the code prints:

在我的电脑上打印代码:

Local:2017-09-02T07:25:46.211+02:00[Europe/Berlin] --> UTC time:2017-09-02T05:25:46.211Z
Local Time 2017-09-02T07:25:46.211+02:00[Europe/Berlin]

I left out the milliseconds from the epoch. You can always get them from System.currentTimeMillis(); as in your question, and they are independent of time zone, so I didn’t find them intersting here.

我遗漏了纪元的毫秒数。您始终可以从System.currentTimeMillis()获取它们;在你的问题中,它们与时区无关,所以我没有发现它们在这里。

I hesitatingly kept your variable name localTime. I think it’s a good name. The modern API has a class called LocalTime, so using that name, only not capitalized, for an object that hasn’t got type LocalTime might confuse some (a LocalTime doesn’t hold time zone information, which we need to keep here to be able to make the right conversion; it also only holds the time-of-day, not the date).

我犹豫地保留你的变量名localTime。我认为这是一个好名字。现代API有一个名为LocalTime的类,所以使用该名称,只是没有大写,对于没有类型LocalTime的对象可能会混淆一些(LocalTime不保存时区信息,我们需要保留这里能够进行正确的转换;它也只能保存时间,而不是日期。

Your conversion from local time to UTC was incorrect and impossible

您从本地时间到UTC的转换是不正确的,也是不可能的

The outdated Date class doesn’t hold any time zone information (you may say that internally it always uses UTC), so there is no such thing as converting a Date from one time zone to another. When I just ran your code on my computer, the first line it printed, was:

过时的Date类不包含任何时区信息(您可能会说它在内部始终使用UTC),因此没有将Date从一个时区转换为另一个时区的事情。当我在计算机上运行您的代码时,它打印的第一行是:

Local:Sat Sep 02 07:25:45 CEST 2017,1504329945967 --> UTC time:Sat Sep 02 05:25:45 CEST 2017-1504322745000

07:25:45 CEST is correct, of course. The correct UTC time would have been 05:25:45 UTC, but it says CEST again, which is incorrect.

07:25:45当然,CEST是正确的。正确的UTC时间是05:25:45 UTC,但它再次说CEST,这是不正确的。

Now you will never need the Date class again, :-) but if you were ever going to, the must-read would be All about java.util.Date on Jon Skeet’s coding blog.

现在你再也不需要Date类了:-)但是如果你要去,必须阅读的是关于Jon Skeet编码博客上的java.util.Date的全部内容。

Question: Can I use the modern API with my Java version?

问题:我可以在Java版本中使用现代API吗?

If using at least Java 6, you can.

如果至少使用Java 6,则可以。

  • In Java 8 and later the new API comes built-in.
  • 在Java 8及更高版本中,新的API内置。

  • In Java 6 and 7 get the ThreeTen Backport, the backport of the new classes (that’s ThreeTen for JSR-310, where the modern API was first defined).
  • 在Java 6和7中获取ThreeTen Backport,这是新类的后端(这是JSR-310的ThreeTen,其中首先定义了现代API)。

  • On Android, use the Android edition of ThreeTen Backport. It’s called ThreeTenABP, and I think that there’s a wonderful explanation in this question: How to use ThreeTenABP in Android Project.
  • 在Android上,使用Android版的ThreeTen Backport。它被称为ThreeTenABP,我认为这个问题有一个很好的解释:如何在Android项目中使用ThreeTenABP。

#4


1  

I strongly recommend using Joda Time http://joda-time.sourceforge.net/faq.html

我强烈建议使用Joda Time http://joda-time.sourceforge.net/faq.html

#5


1  

You have a date with a known timezone (Here Europe/Madrid), and a target timezone (UTC)

您有一个已知时区的日期(此处为欧洲/马德里)和目标时区(UTC)

You just need two SimpleDateFormats:

你只需要两个SimpleDateFormats:

        long ts = System.currentTimeMillis();
        Date localTime = new Date(ts);

        SimpleDateFormat sdfLocal = new SimpleDateFormat ("yyyy/MM/dd HH:mm:ss");
        sdfLocal.setTimeZone(TimeZone.getTimeZone("Europe/Madrid"));

        SimpleDateFormat sdfUTC = new SimpleDateFormat ("yyyy/MM/dd HH:mm:ss");
        sdfUTC.setTimeZone(TimeZone.getTimeZone("UTC"));

        // Convert Local Time to UTC
        Date utcTime = sdfLocal.parse(sdfUTC.format(localTime));
        System.out.println("Local:" + localTime.toString() + "," + localTime.getTime() + " --> UTC time:" + utcTime.toString() + "-" + utcTime.getTime());

        // Reverse Convert UTC Time to Locale time
        localTime = sdfUTC.parse(sdfLocal.format(utcTime));
        System.out.println("UTC:" + utcTime.toString() + "," + utcTime.getTime() + " --> Local time:" + localTime.toString() + "-" + localTime.getTime());

So after see it working you can add this method to your utils:

所以看到它工作后你可以将这个方法添加到你的utils:

    public Date convertDate(Date dateFrom, String fromTimeZone, String toTimeZone) throws ParseException {
        String pattern = "yyyy/MM/dd HH:mm:ss";
        SimpleDateFormat sdfFrom = new SimpleDateFormat (pattern);
        sdfFrom.setTimeZone(TimeZone.getTimeZone(fromTimeZone));

        SimpleDateFormat sdfTo = new SimpleDateFormat (pattern);
        sdfTo.setTimeZone(TimeZone.getTimeZone(toTimeZone));

        Date dateTo = sdfFrom.parse(sdfTo.format(dateFrom));
        return dateTo;
    }

#6


0  

tl;dr

Instant.now()                           // Capture the current moment in UTC.
.atZone( ZoneId.systemDefault() )       // Adjust into the JVM's current default time zone. Same moment, different wall-clock time. Produces a `ZonedDateTime` object.
.toInstant()                            // Extract a `Instant` (always in UTC) object from the `ZonedDateTime` object.
.atZone( ZoneId.of( "Europe/Paris" ) )  // Adjust the `Instant` into a specific time zone. Renders a `ZonedDateTime` object. Same moment, different wall-clock time.
.toInstant()                            // And back to UTC again.

java.time

The modern approach uses the java.time classes that supplanted the troublesome old legacy date-time classes (Date, Calendar, etc.).

现代方法使用java.time类来取代麻烦的旧遗留日期时间类(日期,日历等)。

Your use of the word "local" contradicts the usage in the java.time class. In java.time, "local" means any locality or all localities, but not any one particular locality. The java.time classes with names starting with "Local…" all lack any concept of time zone or offset-from-UTC. So they do not represent a specific moment, they are not a point on the timeline, whereas your Question is all about moments, points on the timeline viewed through various wall-clock times.

您对“local”一词的使用与java.time类中的用法相矛盾。在java.time中,“local”表示任何地点或所有地点,但不是任何一个特定地点。名称以“Local ...”开头的java.time类都缺少时区或从UTC偏移的概念。因此,它们不代表特定的时刻,它们不是时间轴上的一个点,而您的问题是关于时刻,通过各种挂钟时间查看时间轴上的点。

Get the current system time (local time)

获取当前系统时间(当地时间)

If you want to capture the current moment in UTC, use Instant. The Instant class represents a moment on the timeline in UTC with a resolution of nanoseconds (up to nine (9) digits of a decimal fraction).

如果要捕获UTC中的当前时刻,请使用“即时”。 Instant类表示UTC时间轴上的一个时刻,分辨率为纳秒(最多九(9)位小数)。

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

Adjust into a time zone by applying a ZoneId to get a ZonedDateTime. Same moment, same point on the timeline, different wall-clock time.

通过应用ZoneId调整到时区以获取ZonedDateTime。同一时刻,时间轴上的同一点,不同的挂钟时间。

Specify a proper time zone name in the format of continent/region, such as America/Montreal, Africa/Casablanca, or Pacific/Auckland. Never use the 3-4 letter abbreviation such as EST or IST as they are not true time zones, not standardized, and not even unique(!).

以洲/地区的格式指定适当的时区名称,例如America / Montreal,Africa / Casablanca或Pacific / Auckland。切勿使用3-4字母缩写,例如EST或IST,因为它们不是真正的时区,不是标准化的,甚至不是唯一的(!)。

ZoneId z = ZoneId.of( "America/Montreal" ) ;
ZonedDateTime zdt = instant.atZone( z ) ;  // Same moment, different wall-clock time.

As a shortcut, you can skip the usage of Instant to get a ZonedDateTime.

作为快捷方式,您可以跳过使用Instant来获取ZonedDateTime。

ZoneId z = ZoneId.of( "America/Montreal" ) ;
ZonedDateTime zdt = ZonedDateTime.now( z ) ;

Convert Local time to UTC // Works Fine Till here

将本地时间转换为UTC //在这里工作正常

You can adjust from the zoned date-time to UTC by extracting an Instant from a ZonedDateTime.

您可以通过从ZonedDateTime中提取Instant来从分区日期时间调整为UTC。

ZoneId z = ZoneId.of( "America/Montreal" ) ;
ZonedDateTime zdt = ZonedDateTime.now( z ) ;
Instant instant = zdt.toInstant() ;

Reverse the UTC time, back to local time.

反转UTC时间,返回当地时间。

As shown above, apply a ZoneId to adjust the same moment into another wall-clock time used by the people of a certain region (a time zone).

如上所示,应用ZoneId将同一时刻调整为某个区域(时区)的人使用的另一个挂钟时间。

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

ZoneId zDefault = ZoneId.systemDefault() ;  // The JVM's current default time zone.
ZonedDateTime zdtDefault = instant.atZone( zDefault ) ;

ZoneId zTunis = ZoneId.of( "Africa/Tunis" ) ;  // The JVM's current default time zone.
ZonedDateTime zdtTunis = instant.atZone( zTunis ) ;

ZoneId zAuckland = ZoneId.of( "Pacific/Auckland" ) ;  // The JVM's current default time zone.
ZonedDateTime zdtAuckland = instant.atZone( zAuckland ) ;

Going back to UTC from a zoned date-time, call ZonedDateTime::toInstant. Think of it conceptually as: ZonedDateTime = Instant + ZoneId.

从分区日期时间返回UTC,调用ZonedDateTime :: toInstant。从概念上考虑它:ZonedDateTime = Instant + ZoneId。

Instant instant = zdtAuckland.toInstant() ;

All of these objects, the Instant and the three ZonedDateTime objects all represent the very same simultaneous moment, the same point in history.

所有这些对象,Instant和三个ZonedDateTime对象都代表同一时刻,即历史中的同一时刻。

Followed 3 different approaches (listed below) but all the 3 approaches retains the time in UTC only.

遵循3种不同的方法(如下所列),但所有3种方法仅保留UTC时间。

Forget about trying to fix code using those awful Date, Calendar, and GregorianCalendar classes. They are a wretched mess of bad design and flaws. You need never touch them again. If you must interface with old code not yet updated to java.time, you can convert back-and-forth via new conversion methods added to the old classes.

忘记尝试使用那些可怕的Date,Calendar和GregorianCalendar类来修复代码。他们是一个糟糕的设计和缺陷混乱。你不需要再次触摸它们。如果必须与尚未更新到java.time的旧代码接口,则可以通过添加到旧类的新转换方法来回转换。


About java.time

The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.

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

The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.

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

To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.

要了解更多信息,请参阅Oracle教程。并搜索Stack Overflow以获取许多示例和解释。规范是JSR 310。

You may exchange java.time objects directly with your database. Use a JDBC driver compliant with JDBC 4.2 or later. No need for strings, no need for java.sql.* classes.

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

Where to obtain the java.time classes?

从哪里获取java.time类?

  • Java SE 8, Java SE 9, Java SE 10, and later
    • Built-in.
    • Part of the standard Java API with a bundled implementation.
    • 带有捆绑实现的标准Java API的一部分。

    • Java 9 adds some minor features and fixes.
    • Java 9增加了一些小功能和修复。

  • Java SE 8,Java SE 9,Java SE 10和更高版本内置。带有捆绑实现的标准Java API的一部分。 Java 9增加了一些小功能和修复。

  • Java SE 6 and Java SE 7
    • Much of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport.
    • 许多java.time功能都被反向移植到ThreeTen-Backport中的Java 6和7。

  • Java SE 6和Java SE 7许多java.time功能都被反向移植到ThreeTen-Backport中的Java 6和7。

  • Android
    • Later versions of Android bundle implementations of the java.time classes.
    • 更高版本的Android捆绑java.time类的实现。

    • For earlier Android (<26), the ThreeTenABP project adapts ThreeTen-Backport (mentioned above). See How to use ThreeTenABP….
    • 对于早期的Android(<26),ThreeTenABP项目采用ThreeTen-Backport(如上所述)。请参见如何使用ThreeTenABP ....

  • Android更新版本的Android捆绑java.time类的实现。对于早期的Android(<26),ThreeTenABP项目采用ThreeTen-Backport(如上所述)。请参见如何使用ThreeTenABP ....

The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.

ThreeTen-Extra项目使用其他类扩展了java.time。该项目是未来可能添加到java.time的试验场。您可以在这里找到一些有用的课程,如Interval,YearWeek,YearQuarter等。