VictorGram
VictorGram

Reputation: 2661

Java time format for time zone

I am trying to get output for a date in the format (String):

20170801​ ​123030​ ​America/Los_Angeles

But using this code:

SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd hhmmss Z", Locale.US);
sdf.setTimeZone(TimeZone.getTimeZone("America/Los_Angeles"));
System.out.println(sdf.format(new java.util.Date()));

I am getting the output as (notice the zone part):

20174904 024908 -0700

Any idea how to fix it? I need to print "​America/Los_Angeles" instead of "-0700".

Upvotes: 3

Views: 3319

Answers (3)

user7605325
user7605325

Reputation:

As pointed by @Alexandr's answer, there's no built-in pattern in SimpleDateFormat to print the timezone ID. But there's a way to overwrite this.

First you create a formatter with the z pattern, that corresponds to the timezone short name. I'm not sure if the locale matters for this (I know it affects the long names, not sure about the short names, but anyway I'm keeping it).

Then I get the java.text.DateFormatSymbols from the formatter and overwrite the strings that correspond to the short names:

// use "z" (short timezone name)
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd hhmmss z", Locale.US);
sdf.setTimeZone(TimeZone.getTimeZone("America/Los_Angeles"));

// get the java.text.DateFormatSymbols
DateFormatSymbols symbols = sdf.getDateFormatSymbols();
// get the zones names
String[][] zones = symbols.getZoneStrings();
// overwrite zone short names
for (int i = 0; i < zones.length; i++) {
    String zoneId = zones[i][0];
    if ("America/Los_Angeles".equals(zoneId)) {
        zones[i][2] = zoneId; // short name for standard time
        zones[i][4] = zoneId; // short name for Daylight Saving Time
    }
}
// update the symbols in the formatter
symbols.setZoneStrings(zones);
sdf.setDateFormatSymbols(symbols);

System.out.println(sdf.format(new java.util.Date()));

This will print:

20171005 045317 America/Los_Angeles

Note that I changed the zone name only for America/Los_Angeles timezone. You can modify the if to change whatever zones you want - or just remove the if to change it for all zones.

Another detail is that you're using hh for the hours. According to javadoc, this is the Hour in am/pm field (values from 1 to 12). Without the AM/PM designator (pattern a), the output might be ambiguous. You can change this to HH (Hour in day field, values from 0 to 23) or to kk (values from 1 to 24) if you want.


Java new Date/Time API

The old classes (Date, Calendar and SimpleDateFormat) have lots of problems and design issues, and they're being replaced by the new APIs.

If you're using Java 8, consider using the new java.time API. It's easier, less bugged and less error-prone than the old APIs.

If you're using Java 6 or 7, you can use the ThreeTen Backport, a great backport for Java 8's new date/time classes. And for Android, you'll also need the ThreeTenABP (more on how to use it here).

The code below works for both. The only difference is the package names (in Java 8 is java.time and in ThreeTen Backport (or Android's ThreeTenABP) is org.threeten.bp), but the classes and methods names are the same.

Actually, this new API is so straighforward that the code will be exactly the same as the other answers posted by @Juan and @Jens. But there's a subtle difference between those.

Let's suppose I have 2 different ZonedDateTime objects: one represents the current date/time in America/Los_Angeles timezone, and another one represents the same current date/time in Asia/Tokyo timezone:

// current date/time
Instant now = Instant.now();
// get the same instant in different timezones
ZonedDateTime nowLA = now.atZone(ZoneId.of("America/Los_Angeles"));
ZonedDateTime nowTokyo = now.atZone(ZoneId.of("Asia/Tokyo"));
System.out.println(nowLA); // 2017-10-05T05:04:31.253-07:00[America/Los_Angeles]
System.out.println(nowTokyo); // 2017-10-05T21:04:31.253+09:00[Asia/Tokyo]

These dates are:

2017-10-05T05:04:31.253-07:00[America/Los_Angeles]
2017-10-05T21:04:31.253+09:00[Asia/Tokyo]

Now let's see the difference. @Jens's answer sets the timezone in the formatter. This means that all dates will be converted to that specific timezone when formatting (I've just modified the code a little bit, to set the locale directly - instead of using withLocale - but the resulting formatter is equivalent):

// set the timezone in the formatter
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyyMMdd hhmmss VV", Locale.US)
    // use Los Angeles timezone
    .withZone(ZoneId.of("America/Los_Angeles"));
// it converts all dates to Los Angeles timezone
System.out.println(nowLA.format(fmt)); // 20171005 050431 America/Los_Angeles
System.out.println(nowTokyo.format(fmt)); // 20171005 050431 America/Los_Angeles

As the timezone is set in the formatter, both dates are converted to this timezone (including the date and time values):

20171005 050431 America/Los_Angeles
20171005 050431 America/Los_Angeles

While in @Juan's answer, the formatter doesn't have a timezone set, which means it'll preserve the timezone used in the ZonedDateTime objects:

// formatter without a timezone set
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyyMMdd hhmmss VV", Locale.US);
// it keeps the timezone set in the date
System.out.println(nowLA.format(fmt)); // 20171005 050431 America/Los_Angeles
System.out.println(nowTokyo.format(fmt)); // 20171005 090431 Asia/Tokyo

Now the timezone is preserved (and also the date and time values):

20171005 050431 America/Los_Angeles
20171005 090431 Asia/Tokyo

It's a subtle difference, and you must choose the approach that works best for your case. Note that the same issue about hh versus HH also applies here: the variable nowTokyo holds the value equivalent to 21:04:31 (9:04:31 PM in Tokyo), but it's formatted as 090431 - without the AM/PM designator, this time is ambiguous, so IMO the pattern should use HH (so the output would be 210431). But it's up to you to decide.

In the javadoc you can see details about all available patterns.

Upvotes: 4

Jens Hoffmann
Jens Hoffmann

Reputation: 6801

If you can use Java 8, you can use DateTimeFormatter and its symbol V to display the Zone ID:

Instant now = Instant.now();
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyyMMdd hhmmss VV")
    .withLocale(Locale.US)
    .withZone(ZoneId.of("America/Los_Angeles"));
System.out.println(fmt.format(now));

Prints:

20171004 032552 America/Los_Angeles

Note that SimpleDateFormat doesn't support this flag as mentioned in Alexandr's answer.

If you have to start from java.util.Date but can use Java 8 still, you can convert it to an Instant first:

Instant now = new java.util.Date().toInstant();
...

Upvotes: 4

Oleksandr Lykhonosov
Oleksandr Lykhonosov

Reputation: 1388

Looks like the SimpleDateFormat doesn't support what you want.

Here are possible formats:

z   Time zone   General time zone   Pacific Standard Time; PST; GMT-08:00  
Z   Time zone   RFC 822 time zone   -0800  
X   Time zone   ISO 8601 time zone  -08; -0800; -08:00  

You can just add "America/Los_Angeles" after a formatted date:

TimeZone timeZone = TimeZone.getTimeZone("America/Los_Angeles");
DateFormat sdf = new SimpleDateFormat("yyyyMMdd hhmmss", Locale.US);
sdf.setTimeZone(timeZone);
System.out.println(sdf.format(new Date()) + " " + timeZone.getID());

DateFormat sdfWithTimeZone = new SimpleDateFormat("yyyyMMdd hhmmss zzzz", Locale.US);
sdfWithTimeZone.setTimeZone(timeZone);
System.out.println(sdfWithTimeZone.format(new Date()));

Output:

20171004 031611 America/Los_Angeles
20171004 031611 Pacific Daylight Time

Upvotes: 5

Related Questions