Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java SimpleDateFormat interprets 'z' differently on different OS

I've following code (simplified to focus on issue). That prints the timezone information using SimpleDateFormat pattern.

Do you know why z is treated differently on different machines ? And if there is a way to tell Java to treat it uniformly across all the machines ?

This class is being used in JavaMail and that is causing our email headers to include time which is not comply with RFC 2822.

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

public class DateFormatTest {
    String PATTERN = "z";
    SimpleDateFormat simpleDateFormat = new SimpleDateFormat(this.PATTERN);

    public static void main(final String[] args) {
        new DateFormatTest().printTimezone();
    }

    public void printTimezone() {
        System.out.println(this.simpleDateFormat.format(Calendar.getInstance().getTime()));
    }

}

Output : Windows / Mac

PDT

Output : Linux (CentOS Linux release 7.5.1804 (Core)) / Ubuntu 14 / 18

GMT-07:00
like image 573
Abhijeet Apsunde Avatar asked Sep 24 '18 22:09

Abhijeet Apsunde


1 Answers

tl;dr

Never use Calendar. Use java.time classes instead.

For strings in RFC 1123 / RFC 822 format:

OffsetDateTime
.now( ZoneOffset.UTC )
.format( DateTimeFormatter.RFC_1123_DATE_TIME )

Mon, 24 Sep 2018 23:45:21 GMT

To get the current offset-from-UTC in a particular time zone:

ZoneId
.systemDefault()
.getRules()
.getOffset(
    Instant.now() 
)
.toString()

-07:00

Avoid Calendar

You are using terrible old date-time classes that were supplanted years ago by the java.time. Never use those legacy classes; they are an awful wretched mess.

Your particular issue about Calendar behavior is moot as there is no need to ever be using that class again. Even when interoperating with old code not yet updated to java.time, you can convert easily between the legacy & modern classes via new methods added to the old classes.

ZonedDateTime zdt = myGregorianCalendar.toZonedDateTime() ;

…and…

GregorianCalendar gc = GregorianCalendar.from( zdt ) ; 

java.time

Apparently you want the current offset-from-UTC for your current default time zone.

Get the current default time zone, a ZoneId.

ZoneId z = ZoneId.systemDefault() ;  // Or specify ZoneId.of( "Pacific/Auckland" ) or so on.

Ask for the rules in that time zone.

ZoneRules rules = z.getRules() ;

Get the offset-from-UTC in effect in that zone at a certain moment. We will use the current moment, a Instant.

Instant now = Instant.now() ;
ZoneOffset offset = rules.getOffset( now ) ;

Generate a text representing that offset-from-UTC.

String output = "At " + now + " in zone " + z + " the offset is " + offset;

At 2018-09-24T23:38:44.192642Z in zone America/Los_Angeles the offset is -07:00

RFC 1123 / RFC 822

You mentioned an RFC but did not specify. Perhaps RFC 1123 / 822 ?

A formatter for that is built into java.time.

OffsetDateTime nowInUtc = OffsetDateTime.now( ZoneOffset.UTC ) ;
String output = nowInUtc.format( DateTimeFormatter.RFC_1123_DATE_TIME ) ;

Mon, 24 Sep 2018 23:45:21 GMT

ISO 8601

FYI, that RFC 1123 / RFC 822 format is a terrible format. It assumes English. It is difficult for machines to parse, and difficult for humans to read. But I understand that you may need it for outmoded old protocols.

Just know that modern protocols use ISO 8601 standard formats. Conveniently, these formats are used by default in the java.time classes when parsing/generating strings.


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.

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

To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is 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.

Where to obtain the java.time classes?

  • Java SE 8, Java SE 9, Java SE 10, Java SE 11, and later - Part of the standard Java API with a bundled implementation.
  • Java 9 adds some minor features and fixes.
  • Java SE 6 and Java SE 7
  • Most of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport.
  • Android
  • Later versions of Android bundle implementations of the java.time classes.
  • For earlier Android (<26), the ThreeTenABP project adapts ThreeTen-Backport (mentioned above). See How to use 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.

like image 92
Basil Bourque Avatar answered Sep 27 '22 20:09

Basil Bourque