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

我想把这个格林尼治标准时间转换成格林尼治标准时间 + 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));

使用本地机器的 TimeZone。为什么?我知道当“ new Date ()”返回 UTC + 0时间时,为什么当你设置时间为毫秒时,它不再假设时间为 UTC?

有没有可能:

  1. 设置对象上的时间(Calendar/Date/TimeStamp)
  2. (可能)设置初始时间戳(calendar.setTimeZone (...))的 TimeZone
  3. 使用新的 TimeZone (Format ter.setTimeZone (...))格式化时间戳
  4. 返回一个包含新时区时间的字符串(format (calendar.getTime ()))
422766 次浏览

As always, I recommend reading this article about date and time in Java so that you understand it.

The basic idea is that 'under the hood' everything is done in UTC milliseconds since the epoch. This means it is easiest if you operate without using time zones at all, with the exception of String formatting for the user.

Therefore I would skip most of the steps you have suggested.

  1. Set the time on an object (Date, Calendar etc).
  2. Set the time zone on a formatter object.
  3. Return a String from the formatter.

Alternatively, you can use Joda time. I have heard it is a much more intuitive datetime API.

I have try this code

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();
}

and getting this result

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

Had a look about and I don't think theres a timezone in Java that is GMT + 13. So I think you have to use:

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();

(If there is then change "GMT" to that Timezone and remove the 2nd line of code)

OR

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

If you want to set a specific time/date you can also use:

    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);

Understanding how computer time works is very important. With that said I agree that if an API is created to help you process computer time like real time then it should work in such a way that allows you to treat it like real time. For the most part this is the case but there are some major oversights which do need attention.

Anyway I digress!! If you have your UTC offset (better to work in UTC than GMT offsets) you can calculate the time in milliseconds and add that to your timestamp. Note that an SQL Timestamp may vary from a Java timestamp as the way the elapse from the epoch is calculated is not always the same - dependant on database technologies and also operating systems.

I would advise you to use System.currentTimeMillis() as your time stamps as these can be processed more consistently in java without worrying about converting SQL Timestamps to java Date objects etc.

To calculate your offset you can try something like this:

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());

I hope this is helpful!

Joda-Time

The java.util.Date/Calendar classes are a mess and should be avoided.

Update: The Joda-Time project is in maintenance mode. The team advises migration to the java.time classes.

Here's your answer using the Joda-Time 2.3 library. Very easy.

As noted in the example code, I suggest you use named time zones wherever possible so that your programming can handle Daylight Saving Time (DST) and other anomalies.

If you had placed a T in the middle of your string instead of a space, you could skip the first two lines of code, dealing with a formatter to parse the string. The DateTime constructor can take a string in ISO 8601 format.

// © 2013 Basil Bourque. This source code may be used freely forever by anyone taking full responsibility for doing so.
// import org.joda.time.*;
// import org.joda.time.format.*;


// Parse string as a date-time in UTC (no time zone offset).
DateTimeFormatter formatter = org.joda.time.format.DateTimeFormat.forPattern( "yyyy-MM-dd' 'HH:mm:ss" );
DateTime dateTimeInUTC = formatter.withZoneUTC().parseDateTime( "2011-10-06 03:35:05" );


// Adjust for 13 hour offset from UTC/GMT.
DateTimeZone offsetThirteen = DateTimeZone.forOffsetHours( 13 );
DateTime thirteenDateTime = dateTimeInUTC.toDateTime( offsetThirteen );


// Hard-coded offsets should be avoided. Better to use a desired time zone for handling Daylight Saving Time (DST) and other anomalies.
// Time Zone list… http://joda-time.sourceforge.net/timezones.html
DateTimeZone timeZoneTongatapu = DateTimeZone.forID( "Pacific/Tongatapu" );
DateTime tongatapuDateTime = dateTimeInUTC.toDateTime( timeZoneTongatapu );

Dump those values…

System.out.println( "dateTimeInUTC: " + dateTimeInUTC );
System.out.println( "thirteenDateTime: " + thirteenDateTime );
System.out.println( "tongatapuDateTime: " + tongatapuDateTime );

When run…

dateTimeInUTC: 2011-10-06T03:35:05.000Z
thirteenDateTime: 2011-10-06T16:35:05.000+13:00
tongatapuDateTime: 2011-10-06T16:35:05.000+13:00

We can handle this by using offset value

 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();
}

Test Code snippet:

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

Output: 1387353270742 1387335270742

For me, the simplest way to do that is:

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()));

We can get the UTC/GMT time stamp from the given date.

/**
* 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;


}

It will return the UTC time based on passed date.

  • 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 "";
    }
    

I hope this will help others. Thanks!!

The solution is actually quite simple (pure, simple 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));

OUTPUT IS:

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

A quick way is :

String dateText ="Thu, 02 Jul 2015 21:51:46";
long hours = -5; // time difference between places


DateTimeFormatter formatter = DateTimeFormatter.ofPattern(E, dd MMM yyyy HH:mm:ss, Locale.ENGLISH);
LocalDateTime date = LocalDateTime.parse(dateText, formatter);
date = date.with(date.plusHours(hours));


System.out.println("NEW DATE: "+date);

Output

NEW DATE: 2015-07-02T16:51:46

Your approach works without any modification.

Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
// Timestamp for 2011-10-06 03:35:05 GMT
calendar.setTime(new Date(1317872105000L));


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


// Prints 2011-10-06 16:35:05 GMT+13:00
System.out.println(formatter.format(calendar.getTime()));

tl;dr

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

…also…

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

java.time

The Question and most Answers use outdated legacy date-time classes from the earliest versions of Java. These old classes have proven to be troublesome and confusing. Avoid them. Instead use the java.time classes.

ISO 8601

Your input string is nearly in standard ISO 8601 format. Just replace the SPACE in the middle with a T.

String input = "2011-10-06 03:35:05".replace( " " , "T" );

LocalDateTime

Now parse as a LocalDateTime because the input lacks any information about offset-from-UTC or time zone. A LocalDateTime has no concept of offset nor time zone, so it does not represent an actual moment on the timeline.

LocalDateTime ldt = LocalDateTime.parse( input );

ZoneOffset

You seem to be saying that from the business context you know the intention of this string is to represent a moment that is 13 hours ahead of UTC. So we instantiate a ZoneOffset.

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

OffsetDateTime

Apply it to get an OffsetDateTime object. This becomes an actual moment on the timeline.

OffsetDateTime odt = ldt.atOffset( offset);

ZoneId

But then you mention New Zealand. So you had a specific time zone in mind. A time zone is an offset-from-UTC plus a set of rules for handling anomalies such as Daylight Saving Time (DST). So we can specify a ZoneId to a ZonedDateTime rather than a mere offset.

Specify a proper time zone name. 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(!). For example, Pacific/Auckland.

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

ZonedDateTime

Apply the ZoneId.

ZonedDateTime zdt = ldt.atZone( z );

You can easily adjust into another zone for the very same moment on the timeline.

ZoneId zParis = ZoneId.of( "Europe/Paris" );
ZonedDateTime zdtParis = zdt.withZoneSameInstant( zParis );  // Same moment in time, but seen through lens of Paris wall-clock time.

Count from epoch

I strongly recommend against handling date-time values as a count from epoch, such as milliseconds from the start of 1970 UTC. But if you must, create a Instant from such a number.

Instant instant = Instant.ofEpochMilli( 1_317_816_735_000L );

Then assign a time zone as seen above, if desired, to move away from UTC.

ZoneId z = ZoneId.of( "Pacific/Auckland" );
ZonedDateTime zdt = instant.atZone( z );

Your value of 1_317_816_735_000L is:

  • 2011-10-05T12:12:15Z (Wed, 05 Oct 2011 12:12:15 GMT)
  • 2011-10-06T01:12:15+13:00[Pacific/Auckland] (Thursday October 06, 2011 01:12:15 in Auckland New Zealand).

Generate strings

To generate a string in standard ISO 8601 format, simply call toString. Note that ZonedDateTime wisely extends the standard format by appending the name of the time zone in square brackets.

String output = zdt.toString();

For other formats, search Stack Overflow for DateTimeFormatter class. Already covered many times.

Specify a FormatStyle and a Locale.

Locale l = new Locale( "en" , "NZ" );
DateTimeFormatter f = DateTimeFormatter.ofLocalizedDateTime( FormatStyle.MEDIUM ).withLocale( l );
String output = zdt.format( f );

Note that time zone has nothing to do with locale. You can have a Europe/Paris date-time displayed in Japanese language & cultural norms, or a Asia/Kolkata date-time displayed in Portuguese language and Brazil cultural norms.

About java.time

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

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

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

Much of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport and further adapted to Android in ThreeTenABP (see How to use…).

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.

public Timestamp convertLocalTimeToServerDatetime(String dt,String timezone){


String clientDnT = dt ;// "2017-06-01 07:20:00";
try{
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = sdf.parse(clientDnT);
TimeZone tz = TimeZone.getTimeZone(timezone.trim()); // get time zone of user
sdf.setTimeZone(tz);


// Convert to servertime zone
SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
TimeZone tzInAmerica = TimeZone.getDefault();
sdf1.setTimeZone(tzInAmerica);


// assign date to date
String serverDate = sdf1.format(date);


// Convert to servertime zone to Timestamp
Date date2 =  sdf.parse(serverDate);
Timestamp tsm = new Timestamp(date2.getTime());
return  tsm;
}
catch(Exception e){
System.err.println(e);
}


return null;
}

To find duration or time interval with two different time zone

import org.joda.time.{DateTime, Period, PeriodType}


val s1 = "2019-06-13T05:50:00-07:00"
val s2 = "2019-10-09T11:30:00+09:00"


val period = new Period(DateTime.parse(s1), DateTime.parse(s2), PeriodType dayTime())


period.getDays
period.getMinutes
period.getHours

output period = P117DT13H40M

days = 117
minutes = 40
hours = 13

I should like to provide the modern answer.

You shouldn’t really want to convert a date and time from a string at one GMT offset to a string at a different GMT offset and with in a different format. Rather in your program keep an instant (a point in time) as a proper date-time object. Only when you need to give string output, format your object into the desired string.

java.time

Parsing input

    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();

For most purposes Instant is a good choice for storing a point in time. If you needed to make it explicit that the date and time came from GMT, use an OffsetDateTime instead.

Converting, formatting and printing output

    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);

This printed:

06 Oct 2011 16:35:05 GMT+13:00

I specified time zone Pacific/Auckland rather than the offset you mentioned, +13:00. I understood that you wanted New Zealand time, and Pacific/Auckland better tells the reader this. The time zone also takes summer time (DST) into account so you don’t need to take this into account in your own code (for most purposes).

Since Oct is in English, it’s a good idea to give the formatter an explicit locale. GMT might be localized too, but I think that it just prints GMT in all locales.

OOOO in the format patterns string is one way of printing the offset, which may be a better idea than printing the time zone abbreviation you would get from z since time zone abbreviations are often ambiguous. If you want NZDT (for New Zealand Daylight Time), just put z there instead.

Your questions

I will answer your numbered questions in relation to the modern classes in java.time.

Is possible to:

  1. Set the time on an object

No, the modern classes are immutable. You need to create an object that has the desired date and time from the outset (this has a number of advantages including thread safety).

  1. (Possibly) Set the TimeZone of the initial time stamp

The atZone method that I use in the code returns a ZonedDateTime with the specified time zone. Other date-time classes have a similar method, sometimes called atZoneSameInstant or other names.

  1. Format the time stamp with a new TimeZone

With java.time converting to a new time zone and formatting are two distinct steps as shown.

  1. Return a string with new time zone time.

Yes, convert to the desired time zone as shown and format as shown.

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?

It’s not the way you think, which goes nicely to show just a couple of the (many) design problems with the old classes.

  • A Date hasn’t got a time zone. Only when you print it, its toString method grabs your local time zone and uses it for rendering the string. This is true for new Date() too. This behaviour has confused many, many programmers over the last 25 years.
  • A Calender has got a time zone. It doesn’t change when you do calendar.setTime(new Date(1317816735000L));.

Link

Oracle tutorial: Date Time explaining how to use java.time.

display date and time for all timezones

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






static final String ISO8601 = "yyyy-MM-dd'T'HH:mm:ssZ";
DateFormat dateFormat = new SimpleDateFormat(ISO8601);
Calendar c = Calendar.getInstance();
String formattedTime;
for (String availableID : TimeZone.getAvailableIDs()) {
dateFormat.setTimeZone(TimeZone.getTimeZone(availableID));
formattedTime = dateFormat.format(c.getTime());
System.out.println(formattedTime + " " + availableID);
}