转换给定时区的日期/时间 - Java

94

我想把这个GMT时间戳转换为GMT+13:

2011-10-06 03:35:05

我已经尝试了约100种不同的DateFormat、TimeZone、Date、GregorianCalendar等组合,以尝试完成这个非常基本的任务。

这段代码可以为当前时间实现我想要的功能:

Calendar calendar = new GregorianCalendar(TimeZone.getTimeZone("GMT"));

DateFormat formatter = new SimpleDateFormat("dd MMM yyyy HH:mm:ss z");    
formatter.setTimeZone(TimeZone.getTimeZone("GMT+13"));  

String newZealandTime = formatter.format(calendar.getTime());

但是我想设置时间,而不是使用当前时间。

我发现,每当我尝试像这样设置时间:

calendar.setTime(new Date(1317816735000L));

为什么使用本地机器的时区?我知道当使用"new Date()"时,返回的是UTC+0时间,那么为什么设置毫秒数后就不再假定时间是UTC时间呢?

是否可以:

  1. 在对象(Calendar/Date/TimeStamp)上设置时间
  2. (可能)设置初始时间戳的时区(calendar.setTimeZone(...))
  3. 使用新的时区格式化时间戳(formatter.setTimeZone(...)))
  4. 返回一个带有新时区时间的字符串(formatter.format(calendar.getTime()))

一篇帖子中有太多问题了... - Barmaley
同样的两个问题,只是第二个问题会再次提出,并附带一个场景算法以增加清晰度。感谢您的输入。:? - travega
似乎唯一的问题是1317816735000L2011-10-06 03:35:05 GMT的错误时间戳。否则,您的方法是正确的。 - augurar
1
注意,像java.util.Datejava.util.Calendarjava.text.SimpleDateFormat这样的老旧日期时间类现在已经是遗留系统,被 Java 8 及更高版本内置的 java.time 类所取代。请参见 Oracle 的教程 - Basil Bourque
16个回答

78

对我而言,最简单的做法是:

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

Calendar calendar = Calendar.getInstance();
calendar.setTime(new Date());
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");

//Here you say to java the initial timezone. This is the secret
sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
//Will print in UTC
System.out.println(sdf.format(calendar.getTime()));    

//Here you set to your timezone
sdf.setTimeZone(TimeZone.getDefault());
//Will print on your default Timezone
System.out.println(sdf.format(calendar.getTime()));

1
@Charleston 在 date = sdf.parse(review); 这行中,review 是什么意思? - 09Q71AO534
1
@Charleston 但是在SDF中没有这种方法类型,sdf.parse(DateObject)在我使用的JDK.1.7中没有定义。 - 09Q71AO534
我对我的第二个回答感到非常抱歉。我甚至没有测试过它!但现在它已经被测试并且可以正常工作了! - Charleston
3
如何获得实际的日期而不是字符串? - Tulains Córdova
1
也许应该是:SimpleDateFormat("yyyy-MM-dd hh:mm:ss")。 - Marcos
显示剩余2条评论

31

了解计算机时间如何工作非常重要。话虽如此,如果创建一个API来帮助您处理计算机时间,就像实时一样,那么它应该以允许您像实时一样处理它的方式工作。在大多数情况下,这是正确的,但也有一些需要注意的重大疏忽。

无论如何,我离题了!如果您有UTC偏移量(最好使用UTC而不是GMT偏移量),则可以计算毫秒级别的时间并将其添加到时间戳中。请注意,SQL时间戳可能与Java时间戳不同,因为从纪元开始经过的时间计算方式并不总是相同-取决于数据库技术和操作系统。

我建议您使用System.currentTimeMillis()作为时间戳,因为这些可以在Java中更一致地处理,而不必担心将SQL时间戳转换为Java日期对象等。

要计算您的偏移量,您可以尝试类似以下的内容:

Long gmtTime =1317951113613L; // 2.32pm NZDT
Long timezoneAlteredTime = 0L;

if (offset != 0L) {
    int multiplier = (offset*60)*(60*1000);
    timezoneAlteredTime = gmtTime + multiplier;
} else {
    timezoneAlteredTime = gmtTime;
}

Calendar calendar = new GregorianCalendar();
calendar.setTimeInMillis(timezoneAlteredTime);

DateFormat formatter = new SimpleDateFormat("dd MMM yyyy HH:mm:ss z");

formatter.setCalendar(calendar);
formatter.setTimeZone(TimeZone.getTimeZone(timeZone));

String newZealandTime = formatter.format(calendar.getTime());

希望这有帮助!


@user726478 当我们知道时区字符串时,这段代码正确吗? long timezoneAlteredTime = gmtTime + TimeZone.getTimeZone("Asia/Calcutta").getRawOffset(); - Kanagavelu Sugumar
4
不好。时区偏移量不是固定的,它取决于实际日期。 - Dmitry Trifonov
5
如何将一个字符串转换为日期类型? - Tulains Córdova
@KanagaveluSugumar,在这里,偏移量是小时。 - M.Y.

27

简述

Instant.ofEpochMilli( 1_317_816_735_000L )
    .atZone( ZoneId.of( "Pacific/Auckland" ) )
    .format( DateTimeFormatter.ofLocalizedDateTime( FormatStyle.MEDIUM ).withLocale( new Locale( "en" , "NZ" ) ) )

…同样也…

LocalDateTime.parse( "2011-10-06 03:35:05".replace( " " , "T" ) )
    .atZone( ZoneId.of( "Pacific/Auckland" ) )

java.time

问题和大多数答案使用Java早期版本中过时的日期时间类。这些旧类已被证明是麻烦和令人困惑的。请避免使用它们,而是使用java.time类。
ISO 8601
您的输入字符串几乎符合标准的ISO 8601格式。只需将中间的空格替换为T即可。
String input = "2011-10-06 03:35:05".replace( " " , "T" );

LocalDateTime

现在解析为LocalDateTime,因为输入缺乏有关偏移量或时区的任何信息。 LocalDateTime 没有偏移量或时区的概念,因此它不代表时间线上的实际时刻。

LocalDateTime ldt = LocalDateTime.parse( input );

ZoneOffset

您似乎是在说,从业务上下文中,您知道此字符串的意图是表示比UTC快13个小时的时刻。因此,我们实例化一个ZoneOffset

ZoneOffset offset = ZoneOffset.ofHours( 13 ); // 13 hours ahead of UTC, in the far east of the globe.

OffsetDateTime

应用它以获取一个OffsetDateTime对象。这将成为时间轴上的一个实际时刻。

OffsetDateTime odt = ldt.atOffset( offset);

ZoneId

但是你提到了新西兰。所以你有一个特定的时区在脑海中。时区是距离UTC的偏移量加上一组处理异常情况(如夏令时)的规则。因此,我们可以将ZoneId指定给ZonedDateTime,而不仅仅是一个简单的偏移量。

请指定正确的时区名称。永远不要使用3-4个字母的缩写,例如ESTIST,因为它们不是真正的时区,没有标准化,甚至不是唯一的(!)。例如,Pacific/Auckland

ZoneId z = ZoneId.of( "Pacific/Auckland" );

ZonedDateTime

应用 ZoneId

ZonedDateTime zdt = ldt.atZone( z );

你可以在时间轴上同一时刻轻松调整到另一个区域。
ZoneId zParis = ZoneId.of( "Europe/Paris" );
ZonedDateTime zdtParis = zdt.withZoneSameInstant( zParis );  // Same moment in time, but seen through lens of Paris wall-clock time.

从纪元开始计数

我强烈建议不要将日期时间值处理为从纪元开始的计数,比如从1970年UTC开始的毫秒数。但如果你必须这样做,可以使用一个Instant对象从这样的数字中创建。

Instant instant = Instant.ofEpochMilli( 1_317_816_735_000L );

如果需要,可以像上面所示一样分配一个时区,以远离UTC。
ZoneId z = ZoneId.of( "Pacific/Auckland" );
ZonedDateTime zdt = instant.atZone( z );

您的值为1_317_816_735_000L

  • 2011-10-05T12:12:15Z(2011年10月5日星期三,格林威治标准时间12:12:15)
  • 2011-10-06T01:12:15+13:00[Pacific/Auckland](新西兰奥克兰时间2011年10月6日星期四01:12:15)

生成字符串

要生成标准ISO 8601格式的字符串,只需调用toString。请注意,ZonedDateTime明智地通过在方括号中附加时区名称来扩展标准格式。

String output = zdt.toString();

对于其他格式,请在 Stack Overflow 中搜索 DateTimeFormatter 类。已经有很多相关的讨论了。
指定一个 FormatStyle 和一个 Locale
Locale l = new Locale( "en" , "NZ" );
DateTimeFormatter f = DateTimeFormatter.ofLocalizedDateTime( FormatStyle.MEDIUM ).withLocale( l );
String output = zdt.format( f );

请注意,时区与语言环境无关。您可以将Europe/Paris的日期和时间显示为日本语言和文化规范,或将Asia/Kolkata的日期和时间显示为葡萄牙语和巴西文化规范。
关于java.time java.time框架内置于Java 8及更高版本中。这些类替代了麻烦的旧日期时间类,如java.util.Date.Calendarjava.text.SimpleDateFormatJoda-Time项目现在处于维护模式,建议迁移到java.time。
要了解更多信息,请参见Oracle教程。并在Stack Overflow上搜索许多示例和说明。

许多java.time的功能被移植到Java 6和7中,可以使用ThreeTen-Backport进行进一步适配,并在ThreeTenABP中适用于Android(请参见如何使用…)。

ThreeTen-Extra项目通过添加其他类来扩展java.time。该项目是java.time可能未来添加的可靠依据。您可能会发现一些有用的类,例如IntervalYearWeekYearQuarter等。


26

一如既往,我建议阅读Java中有关日期和时间的文章,以便您理解它。

基本思想是,在“引擎盖下”,所有操作都是以自纪元以来的UTC毫秒为单位完成的。这意味着,如果您不使用时区(用户字符串格式化除外),则操作最简单。

因此,我会跳过您建议的大部分步骤。

  1. 在对象(Date、Calendar等)上设置时间。
  2. 在格式化程序对象上设置时区。
  3. 从格式化程序返回一个字符串。

或者,您可以使用Joda time。我听说它是一个更直观的日期时间API。


嗨,Bringer128,感谢您的回复。对我来说,似乎没有一种直截了当的方法可以根据给定的时区调整时间戳,这简直让人感到荒谬。如果它只是改变格式化字符串中的时区标签,那么为什么要包括此功能呢?我将尝试使用Joda时间库。谢谢。 - travega
@travega,有一个简单的方法,如果你将时间表示为UTC并使用SimpleDateFormat格式化它。不仅是“Z”部分(时区标签)被修改,整个日期也会被修改。当两个不同的SimpleDateFormat对象在格式化相同日期时设置了不同的时区,请检查结果。 - user545680
如何获取实际日期而不是字符串? - Tulains Córdova
@TulainsCórdova 我认为您需要更详细地解释一下。您能否创建一个问题或搜索相关问题,以解决您的具体问题? - user545680

13

解决方案实际上非常简单(纯粹的Java):

System.out.println(" NZ Local Time: 2011-10-06 03:35:05");
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime localNZ = LocalDateTime.parse("2011-10-06 03:35:05",formatter);
ZonedDateTime zonedNZ = ZonedDateTime.of(localNZ,ZoneId.of("+13:00"));
LocalDateTime localUTC = zonedNZ.withZoneSameInstant(ZoneId.of("UTC")).toLocalDateTime();
System.out.println("UTC Local Time: "+localUTC.format(formatter));

输出结果为:

 NZ Local Time: 2011-10-06 03:35:05
UTC Local Time: 2011-10-05 14:35:05

1
这个问题在4年前就已经解决了,你的解决方案并没有增加任何新内容。 - travega
1
我认为这是使用Java 8的新方法,但答案质量较低。 - Przemek

5

我查看了一下,Java中没有GMT +13的时区。因此,我认为您需要使用:

Calendar calendar = Calendar.getInstance();
//OR Calendar.getInstance(TimeZone.getTimeZone("GMT"));

calendar.set(Calendar.HOUR_OF_DAY, calendar.get(Calendar.HOUR_OF_DAY)+13);

Date d = calendar.getTime();

如果有时区信息,则将“GMT”更改为该时区,并删除第二行代码。

或者

SimpleDateFormat df = new SimpleDateFormat();
df.setTimeZone(TimeZone.getTimeZone("GMT+13"));
System.out.println(df.format(c.getTime()));

如果您想设置特定的时间/日期,您也可以使用以下方式:

    calendar.set(Calendar.DATE, 15);
calendar.set(Calendar.MONTH, 3);
calendar.set(Calendar.YEAR, 2011);
calendar.set(Calendar.HOUR_OF_DAY, 13); 
calendar.set(Calendar.MINUTE, 45);
calendar.set(Calendar.SECOND, 00);

1
嗨,Craig,感谢您的回复。Java不支持/不支持ISO时间标准GMT是独立于Java调节的全球时间标准。时间/日历API遵守标准,GMT+13指的是任何GMT+12区域的NZDT或夏令时。关于您的示例代码,我已经在我的IP中提到,我已经成功地解决了您的情况,但我面临的问题是定义时间戳并输出原始时间戳的准确时区特定变体,而不是您的代码所做的当前时间的派生物。谢谢,T。 - travega

3
我尝试过这段代码。
try{
            SimpleDateFormat sdf = new SimpleDateFormat("dd-MM-yyyy hh:mm:ss Z");
            Date datetime = new Date();

            System.out.println("date "+sdf.format(datetime));

            sdf.setTimeZone(TimeZone.getTimeZone("GMT"));

            System.out.println("GMT "+ sdf.format(datetime));

            sdf.setTimeZone(TimeZone.getTimeZone("GMT+13"));

            System.out.println("GMT+13 "+ sdf.format(datetime));

            sdf.setTimeZone(TimeZone.getTimeZone("UTC"));

            System.out.println("utc "+sdf.format(datetime));

            Calendar calendar = new GregorianCalendar(TimeZone.getTimeZone("GMT"));

            DateFormat formatter = new SimpleDateFormat("dd MMM yyyy HH:mm:ss z");    
            formatter.setTimeZone(TimeZone.getTimeZone("GMT+13"));  

            String newZealandTime = formatter.format(calendar.getTime());

            System.out.println("using calendar "+newZealandTime);

        }catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

并获得此结果

date 06-10-2011 10:40:05 +0530
GMT 06-10-2011 05:10:05 +0000 // here getting 5:10:05
GMT+13 06-10-2011 06:10:05 +1300 // here getting 6:10:05
utc 06-10-2011 05:10:05 +0000
using calendar 06 Oct 2011 18:10:05 GMT+13:00

嗨Pratik,感谢您的输入,但您没有抓住重点。正如我在我的IP中提到的那样,我已经让它在当前时间戳下工作了。问题在于设置时间戳,然后生成准确的时区特定输出。 - travega
是的,如果更改时间也可以工作。时区已设置为格式对象,因此您传递的任何时间都将基于指定到格式对象中的时区。 - Pratik
嗯...我已经尝试添加:datetime.setTime(Timestamp.valueOf("2011-10-06 03:35:05").getTime());但仍然不起作用。当您设置时间时,您是如何得出它能正常工作的呢? - travega
@Pratik:我该如何获取AST时区。 - Onic Team

3
我们可以通过使用偏移值来处理这个问题。
 public static long convertDateTimeZone(long lngDate, String fromTimeZone,
        String toTimeZone){
    TimeZone toTZ = TimeZone.getTimeZone(toTimeZone);
    Calendar toCal = Calendar.getInstance(toTZ);        

    TimeZone fromTZ = TimeZone.getTimeZone(fromTimeZone);
    Calendar fromCal = Calendar.getInstance(fromTZ);
    fromCal.setTimeInMillis(lngDate);
    toCal.setTimeInMillis(fromCal.getTimeInMillis()
            + toTZ.getOffset(fromCal.getTimeInMillis())
            - TimeZone.getDefault().getOffset(fromCal.getTimeInMillis()));      
    return toCal.getTimeInMillis();
}

测试代码片段:

 System.out.println(new Date().getTime())
 System.out.println(convertDateTimeZone(new Date().getTime(), TimeZone
                .getDefault().getID(), "EST"));

输出: 1387353270742 1387335270742


2
我们可以从给定的日期中获取UTC/GMT时间戳。
/**
 * Get the time stamp in GMT/UTC by passing the valid time (dd-MM-yyyy HH:mm:ss)
 */
public static long getGMTTimeStampFromDate(String datetime) {
    long timeStamp = 0;
    Date localTime = new Date();

    String format = "dd-MM-yyyy HH:mm:ss";
    SimpleDateFormat sdfLocalFormat = new SimpleDateFormat(format);
    sdfLocalFormat.setTimeZone(TimeZone.getDefault());

    try {

        localTime = (Date) sdfLocalFormat.parse(datetime); 

        Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"),
                Locale.getDefault());
        TimeZone tz = cal.getTimeZone();

        cal.setTime(localTime);

        timeStamp = (localTime.getTime()/1000);
        Log.d("GMT TimeStamp: ", " Date TimegmtTime: " + datetime
                + ", GMT TimeStamp : " + localTime.getTime());

    } catch (Exception e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }

    return timeStamp;

}

它将根据传递的日期返回UTC时间。
  • We can do reverse like UTC time stamp to current date and time(vice versa)

        public static String getLocalTimeFromGMT(long gmtTimeStamp) {
                 try{
                        Calendar calendar = Calendar.getInstance();
                        TimeZone tz = TimeZone.getDefault();
                        calendar.setTimeInMillis(gmtTimeStamp * 1000);
        //              calendar.add(Calendar.MILLISECOND, tz.getOffset(calendar.getTimeInMillis())); 
                        SimpleDateFormat sdf = new SimpleDateFormat("dd-MM-yyyy HH:mm:ss");
                        Date currenTimeZone = (Date) calendar.getTime();
                        return sdf.format(currenTimeZone);
                    }catch (Exception e) {
                    }
                    return "";  
                }
    
我希望这可以帮助其他人。谢谢!

2
我想提供现代的答案。
你真的不应该想要将一个具有不同GMT偏移量和不同格式的字符串日期和时间转换为另一个字符串。相反,在程序中保持一个瞬间(一个时间点)作为正确的日期时间对象。只有在需要输出字符串时,将对象格式化为所需的字符串。

java.time

解析输入
    DateTimeFormatter formatter = new DateTimeFormatterBuilder()
            .append(DateTimeFormatter.ISO_LOCAL_DATE)
            .appendLiteral(' ')
            .append(DateTimeFormatter.ISO_LOCAL_TIME)
            .toFormatter();

    String dateTimeString = "2011-10-06 03:35:05";
    Instant instant = LocalDateTime.parse(dateTimeString, formatter)
            .atOffset(ZoneOffset.UTC)
            .toInstant();

大多数情况下,使用 Instant 存储时间点是一个不错的选择。如果你需要明确表示日期和时间来自 GMT,请使用 OffsetDateTime
转换、格式化和打印输出。
    ZoneId desiredZone = ZoneId.of("Pacific/Auckland");
    Locale desiredeLocale = Locale.forLanguageTag("en-NZ");
    DateTimeFormatter desiredFormatter = DateTimeFormatter.ofPattern(
            "dd MMM uuuu HH:mm:ss OOOO", desiredeLocale);

    ZonedDateTime desiredDateTime = instant.atZone(desiredZone);
    String result = desiredDateTime.format(desiredFormatter);
    System.out.println(result);

这是一段打印时间的代码,指定了太平洋/奥克兰时区而非你提到的+13:00偏移量。我理解你想要新西兰时间,太平洋/奥克兰更好地告诉读者这一点。该时区还考虑了夏令时(DST),因此您不需要在自己的代码中考虑这一点(对于大多数用途而言)。由于Oct是英文,最好为格式化程序提供明确的语言环境。GMT也可能本地化,但我认为它在所有语言环境下都会打印GMT。格式模式字符串中的OOOO是打印偏移量的一种方式,这可能比打印从z获取的时区缩写更好,因为时区缩写经常是有歧义的。如果您想要NZDT(新西兰夏令时),只需将z放在那里即可。关于现代java.time类,我将回答您编号的问题。
可能吗:
1. 在对象上设置时间 答:不行,现代类是不可变的。您需要从一开始就创建具有所需日期和时间的对象(这具有许多优点,包括线程安全)。
2. (可能)设置初始时间戳的时区 答:我在代码中使用的atZone方法返回具有指定时区的ZonedDateTime。其他日期时间类也有类似的方法,有时称为atZoneSameInstant或其他名称。
3. 使用新的时区格式化时间戳 答:使用java.time,将转换为新时区和格式化是两个不同的步骤,如下所示。
4. 返回具有新时区时间的字符串。 答:是的,按照以下所示进行转换到所需的时区并进行格式化。

I found that anytime I try to set the time like this:

calendar.setTime(new Date(1317816735000L));

the local machine's TimeZone is used. Why is that?

这不是你想象的那样,这很好地展示了旧类中的一些(许多)设计问题之一。
  • Date 没有时区。只有在打印它时,它的 toString 方法会获取您本地的时区并用于呈现字符串。对于 new Date() 也是如此。这种行为已经困扰了许多程序员超过25年。
  • Calendar 一个时区。当您执行 calendar.setTime(new Date(1317816735000L)); 时,它不会改变。

链接

Oracle 教程:日期时间 解释如何使用 java.time。


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