3

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".

VictorGram
  • 2,521
  • 7
  • 48
  • 82
  • [You have to use uppercase 'M' for months](https://docs.oracle.com/javase/7/docs/api/java/text/SimpleDateFormat.html). Lowercase 'm' means _minutes_ – Juan Carlos Mendoza Oct 04 '17 at 21:55
  • Why not remove the `Z` and concat the name manually? – shmosel Oct 04 '17 at 22:52
  • 2
    You are using troublesome old date-time classes that are now legacy, supplanted by the java.time classes. – Basil Bourque Oct 05 '17 at 14:02
  • Hi Patty, do the proposed answers help you with your requirement? In that case it would be nice if you accepted one of them. If not please let us know why this doesn't work so we can provide additional help. – Jens Hoffmann Oct 07 '17 at 11:43

3 Answers3

5

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
Oleksandr Lykhonosov
  • 1,138
  • 12
  • 25
4

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();
...
Jens Hoffmann
  • 6,699
  • 2
  • 25
  • 31
  • 2
    I suspect those `hh` should be uppercase `HH`. I think we can assume 24-hour clock as there is no am/pm indicator. – Basil Bourque Oct 05 '17 at 14:05
  • 2
    Indeed @Patty, be aware of the `hh` vs `HH` issue mentioned above as well as the subtle difference between setting the timezone in the formatter vs. not setting it as mentioned in [@Hugo's detailed answer](https://stackoverflow.com/a/46585345/396255). These variances depend on your requirements. – Jens Hoffmann Oct 05 '17 at 17:58
4

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.