How can I get Date.toString() to produce an output that SimpleDateFormat can parse correctly for Dates around 1 Jan 1970 (I assume this applies to winter of 1968 and 1969 as well)
If I run the following,
System.out.println(TimeZone.getDefault());
Date date = new Date(0);
SimpleDateFormat sdf = new SimpleDateFormat("EEE MMM dd HH:mm:ss zzz yyyy");
Date date2 = sdf.parse(date.toString());
System.out.println("date: " + date);
System.out.println("date2: " + date2);
Date date3 = sdf.parse(date2.toString());
System.out.println("date3: " + date3);
This prints
sun.util.calendar.ZoneInfo[id="Europe/London",offset=0,dstSavings=3600000,useDaylight=true,transitions=242,lastRule=java.util.SimpleTimeZone[id=Europe/London,offset=0,dstSavings=3600000,useDaylight=true,startYear=0,startMode=2,startMonth=2,startDay=-1,startDayOfWeek=1,startTime=3600000,startTimeMode=2,endMode=2,endMonth=9,endDay=-1,endDayOfWeek=1,endTime=3600000,endTimeMode=2]]
date: Thu Jan 01 01:00:00 GMT 1970
date2: Thu Jan 01 02:00:00 GMT 1970
date3: Thu Jan 01 03:00:00 GMT 1970
The problem is that London was in BST on 1 Jan 1970. So the correct date should be either
date: Thu Jan 01 01:00:00 BST 1970
or
date: Thu Jan 01 00:00:00 GMT 1970
but it seems a confusion of the two.
And while I would love to not support java.util.Date, it's not an option for me.
Your input is invalid as BST
(British Summer Time) was not in effect during the winter.
BST
cannot be reliably parsed, as it is a non-standard non-unique pseudo-zone.
There is no need to mess around with SimpleDateFormat
. Let the modern java.time classes do the heavy lifting.
And while I would love to not support
java.util.Date
, it's not an option for me.
At the edges of your code, convert to-from the legacy and modern classes.
// Convert from legacy to modern.
Instant instant = myJavaUtilDate.toInstant() ;
// Convert from modern to legacy.
java.util.Date myJavaUtilDate = Date.from( instant ) ;
BST
in winterApparently the “B” in your BST
is meant to be British. But BST
in that context means British Summer Time. This means Daylight Saving Time (DST) which is engaged in the summer time, not the winter. So your input string of a January date combined with BST
is nonsensical.
There is a further complication to your example of a moment in 1970 with a British time zone.
The practice of DST in Britain using an offset of one hour ahead of UTC (+01:00) in summer, and an offset of zero (+00:00) in the winter for Standard Time is current practice. That has not always been the case.
Back in 1970, Britain was trialling a “double-summertime”. In that experiment of 1968-1971, winter time was one hour ahead of UTC rather than zero, and summer time was two hours ahead of UTC instead of the one hour used nowadays. This put British time more in common with continental Europe and was hoped to reduce accidents.
So if we adjust a moment in January of 1970, we expect to jump to one hour ahead for time zone Europe/London
. Whereas a moment in January of 2019, we expect no jump, the time-of-day in Britain will be the same as UTC (an offset-from-UTC of zero hours-minutes-seconds).
Avoid these 2-4 character pseudo-zones such as BST
. They are not standardized. They are not even unique! So BST
can be interpreted to be the time zone Pacific/Bougainville
just as well as British Summer Time
.
Specify a proper time zone name in the format of Continent/Region
, such as America/Montreal
, Africa/Casablanca
, or Pacific/Auckland
. Never use the 2-4 letter abbreviation such as BST
or EST
or IST
as they are not true time zones, not standardized, and not even unique(!).
You can convert between the legacy and modern date-time classes easily. New conversion methods have been added to the old classes. Look for from
, to
, and valueOf
methods, per the naming conventions.
java.util.Date
⇄ java.time.Instant
java.util.GregorianCalendar
⇄ java.time.ZonedDateTime
java.sql.Date
⇄ java.time.LocalDate
java.sql.Timestamp
⇄ java.time.Instant
Your input string of 00:00 on January 1, 1970 happens to be the epoch reference date used by both the legacy and modern date-time classes. We have a constant for that value.
Instant epoch = Instant.EPOCH ;
instant.toString(): 1970-01-01T00:00:00Z
See that same moment through your time zone of Europe/London
.
ZoneId z = ZoneId.of( "Europe/London" ) ;
ZonedDateTime zdt = epoch.atZone( z ) ;
zdt.toString(): 1970-01-01T01:00+01:00[Europe/London]
Notice that above-mentioned Double-Summertime experiment in effect then. If we try the same code for 2019, we get an offset-from-UTC of zero.
ZonedDateTime zdt2019 =
Instant
.parse( "2019-01-01T00:00Z" )
.atZone( ZoneId.of( "Europe/London" ) )
;
zdt2019.toString(): 2019-01-01T00:00Z[Europe/London]
To convert to a java.util.Date
, we need an java.time.Instant
object. An Instant
represents a moment in UTC. We can extract an Instant
from our ZonedDateTime
object, effectively adjusting from a zone to UTC. Same moment, different wall-clock time.
Instant instant = zdt.toInstant():
We should now be back where we started, at the epoch reference date of 1970-01-01T00:00:00Z.
instant.toString(): 1970-01-01T00:00:00Z
To get the java.util.Date
object you may need to interoperate with old code not yet updated to java.time classes, use the new Date.from
method added to the old class.
java.util.Date d = Date.from( instant ) ; // Same moment, but with possible data-loss as nanoseconds are truncated to milliseconds.
d.toString(): Thu Jan 01 00:00:00 GMT 1970
By the way, be aware of possible data-loss when converting from Instant
to Date
. The modern classes have a resolution of nanoseconds while the legacy classes use milliseconds. So part of your fractional second may be truncated.
See all the code above run live at IdeOne.com.
To convert the other direction, use the Date::toInstant
method.
Instant instant = d.toInstant() ;
Avoid using text in custom formats for exchanging date-time values. When serializing date-time values as human-readable text, use only the standard ISO 8601 formats. The java.time classes use these formats by default.
Those strings you were experimenting with parsing are a terrible format and should never be used for data-exchange.
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?
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.
I may cite https://bugs.openjdk.java.net/browse/JDK-6609362?jql=text%20~%20%22epoch%20gmt%22:
Please use Z to format and parse historical time zone offset changes to avoid confusions with historical time zone name changes.
public class Test { public static void main(String[] args) throws ParseException { TimeZone.setDefault(TimeZone.getTimeZone("Europe/London")); SimpleDateFormat f = new SimpleDateFormat("EEE MMM dd HH:mm:ss Z yyyy"); Date d = new Date(0); for (int i = 0; i < 10; i++) { String s = f.format(d); System.out.println(s); d = f.parse(s); } } } ```
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With