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.
tl;dr
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 ) ;
No BST in winter
Apparently 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.
Double-Summertime
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 pseudo-zones
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(!).
Convert
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
Converting
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() ;
ISO 8601
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.
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.
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);
}
} } ```
This is why I hate the Date Library.
As implied, BST should be used during the summer, and the calendar defines it as such.
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]]
Except, for, TIL of The adventures of year-round British Summer Time!
A further inquiry during 1966–67 led the government of Harold Wilson to introduce the British Standard Time experiment, with Britain remaining on GMT+1 throughout the year. This took place between 27 October 1968 and 31 October 1971, when there was a reversion to the previous arrangement.
If you test dates around this period you will find the dates drifting off by an hour each parse, up to the switchover points.
The time code for Europe/London is GMT, with daylight savings using BST.
The toString method of Date "normalizes" the output by removing daylight savings time to pick what time zone to print. The options are GMT and BST. The Europe/London time of 01:00:00 printed as 01:00:00 GMT even though it is operating on GMT+1 time. So in other words, date.toString()does not work properly for this swath of time around the epoch because it uses GMT as a time zone for a time zone that is ostensibly not GMT/CET. The time itself is correct, but not the time zone.
The "simplest" solution I can come up with is relatively nasty from a sanity checkpoint, but can probably be made more elegant.
private static final Date experimentEnd = new Date(1971-1900, 11-1, 11);
private static final Date experimentStart = (new Date(1968-1900, 10-1, 26));
private static boolean bstExperimentTime(Date date) {
return date.after(experimentStart) && date.before(experimentEnd);
}
public static String forDateParsing(Date date) {
if(bstExperimentTime(date))
return date.toString().replace("GMT", "CET");
return date.toString();
}
public static String forDatePrinting(Date date) {
if(bstExperimentTime(date))
return date.toString().replace("GMT", "BST");
return date.toString();
}
Any date you need to parse with default "Europe/London" time zone needs to be passed through the parse formatter to convert GMT -> CET, which is the correct GMT+1.
Any date you need to print with default "Europe/London" time zone needs to be passed through the parse formatter to convert GMT -> BST, which is the correct display.
Related
I need get the same day in this year.
Example: Now is 2019 year and the variable contains value 15 July 2022, so I need to get 15 July 2019 then. It works for all dates except February when it has an extra day in one year and doesn't have this day in this year, example: 29 February 2020 will return me the next day: 1 March 2019, but I need in this case to return the previous day: 28 February 2019. How I can adjust my logic so that it will work in this way?
public static java.util.Date getThisDateInThisYear(java.util.Date date) {
GregorianCalendar gc = new GregorianCalendar();
gc.setTime(date);
Date today = new Date();
GregorianCalendar gcToday = new GregorianCalendar();
gcToday.setTime(today);
gc.set(GregorianCalendar.YEAR, gcToday.get(GregorianCalendar.YEAR));
return gc.getTime();
}
first calc the difference of years and add the result to date
public Date getThisDateInThisYear(Date date) {
Calendar c = Calendar.getInstance();
int thisYear = c.get(Calendar.YEAR)
c.setTime(date);
int diff = thisYear - c.get(Calendar.YEAR);
c.add(Calendar.YEAR, diff);
return c.getTime();
}
I tested with 2016-02-29 and 2020-02-29, and both return 2019-02-28.
tl;dr
Use modern java.time classes. Never use Date/Calendar.
29 February 2020 will return me next day: 1st March 2019
LocalDate // Represent a date-only value, without time-of-day and without time zone.
.of( 2020 , Month.FEBRUARY , 29 ) // Specify a date. Here, Leap Day of 2020. Returns a `LocalDate` object.
.minusYears( 1 ) // Intelligently move backwards in time one year. Returns another `LocalDate` object, per immutable objects pattern.
.toString() // Generate text representing the value of this date, in standard ISO 8601 format of YYYY-MM-DD.
When run at IdeOne.com:
2019-02-28
Avoid legacy date-time classes
You are using terrible date-time classes that are now legacy, supplanted entirely by the java.time classes.
LocalDate
The LocalDate class represents a date-only value without time-of-day and without time zone or offset-from-UTC.
A time zone is crucial in determining a date. For any given moment, the date varies around the globe by zone. For example, a few minutes after midnight in Paris France is a new day while still “yesterday” in Montréal Québec.
If no time zone is specified, the JVM implicitly applies its current default time zone. That default may change at any moment during runtime(!), so your results may vary. Better to specify your desired/expected time zone explicitly as an argument.
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 EST or IST as they are not true time zones, not standardized, and not even unique(!).
ZoneId z = ZoneId.of( "America/Montreal" ) ;
LocalDate today = LocalDate.now( z ) ;
If you want to use the JVM’s current default time zone, ask for it and pass as an argument. If omitted, the code becomes ambiguous to read in that we do not know for certain if you intended to use the default or if you, like so many programmers, were unaware of the issue.
ZoneId z = ZoneId.systemDefault() ; // Get JVM’s current default time zone.
Or specify a date. You may set the month by a number, with sane numbering 1-12 for January-December.
LocalDate ld = LocalDate.of( 2020 , 2 , 29 ) ; // Years use sane direct numbering (1986 means year 1986). Months use sane numbering, 1-12 for January-December.
Or, better, use the Month enum objects pre-defined, one for each month of the year. Tip: Use these Month objects throughout your codebase rather than a mere integer number to make your code more self-documenting, ensure valid values, and provide type-safety. Ditto for Year & YearMonth.
LocalDate ld = LocalDate.of( 2020 , Month.FEBRUARY , 29 ) ;
Date-time math
You can do date-time math in a few ways with *java.time. One way is by calling plus or minus and passing a Period or Duration. Another way is calling the convenience methods such as plusYears or minusYears.
The LocalDate class seems to give just the behavior you want with these methods.
To be clear:
2020 is a Leap Year. So 2020-02-29 is a valid date.
2018, 2019, and 2021 are not. The 29th is not a valid date in these years.
See the examples below run live at IdeOne.com.
From leap year
Let's start on Leap Day, the 29th, then add a year and subtract a year. We expect to see 28th on both.
LocalDate leapDay2020 = LocalDate.of( 2020 , Month.FEBRUARY , 29 );
LocalDate yearBeforeLeap = leapDay2020.minusYears( 1 );
LocalDate yearAfterLeap = leapDay2020.plusYears( 1 );
System.out.println( "leapDay2020.toString(): " + leapDay2020 );
System.out.println( "yearBeforeLeap.toString(): " + yearBeforeLeap );
System.out.println( "yearAfterLeap.toString(): " + yearAfterLeap );
Indeed that is what we get.
leapDay2020.toString(): 2020-02-29
yearBeforeLeap.toString(): 2019-02-28
yearAfterLeap.toString(): 2021-02-28
From non-Leap Year
Now let's start in a non-Leap Year on the 28th of February, then add & subtract a year. We expect to see 28th in all three. The 29th in the Leap year of 2020 is ignored.
LocalDate nonLeap2019 = LocalDate.of( 2019 , Month.FEBRUARY , 28 );
LocalDate yearBeforeNonLeapIntoNonLeap = nonLeap2019.minusYears( 1 );
LocalDate yearBeforeNonLeapIntoLeap = nonLeap2019.plusYears( 1 );
System.out.println( "nonLeap2019.toString(): " + nonLeap2019 );
System.out.println( "yearBeforeNonLeapIntoNonLeap.toString(): " + yearBeforeNonLeapIntoNonLeap );
System.out.println( "yearBeforeNonLeapIntoLeap.toString(): " + yearBeforeNonLeapIntoLeap );
nonLeap2019.toString(): 2019-02-28
yearBeforeNonLeapIntoNonLeap.toString(): 2018-02-28
yearBeforeNonLeapIntoLeap.toString(): 2020-02-28
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.
Also you can check if year is leap year based on that you can subtract the date
public static boolean isLeapYear(int year) {
Calendar cal = Calendar.getInstance();
cal.set(Calendar.YEAR, year);
return cal.getActualMaximum(Calendar.DAY_OF_YEAR) > 365;
}
I don't see any problem with java.util.Calendar. In JDK 1.8 this Java class got a major rework. So the following example code works fine:
SimpleDateFormat sf = new SimpleDateFormat("dd MMM yyyy");
Date date=sf.parse("29 Feb 2020");
System.out.println("date="+date);
Calendar calendar=Calendar.getInstance();
calendar.setTime(date);
calendar.add(Calendar.YEAR, -1);
System.out.println("date="+calendar.getTime());
calendar.add(Calendar.YEAR, 1);
System.out.println("date="+calendar.getTime());
It gives the same result as Basil Bourque wants it to have:
date=Sat Feb 29 00:00:00 CET 2020
date=Thu Feb 28 00:00:00 CET 2019
date=Fri Feb 28 00:00:00 CET 2020
Basically the Java class now uses the same ZoneInfo etc.. classes as the new classes also do.
I have some TimeStamp and I have date format "EEEE, MMM dd, yyyy hh:mm a zzz". But I don't know how I can show this timestamp with timezone. When I trying to show it I get wrong DateTime or wrong timezone
example1:
Calendar calendar = Calendar.getInstance();
calendar.setTime(new Date(device.getUpdatedDate().getTime())); // here is 2018-07-09 20:02:26.506000
SimpleDateFormat sdf = new SimpleDateFormat(EMAIL_DATE_FORMAT);
sdf.format(calendar.getTime()); // i have wrong timezone
and i get Monday, Jul 09, 2018 08:02 PM EEST
but when i add sdf.setTimeZone(TimeZone.getTimeZone("HST")); i have right timezone and wrong time
Monday, Jul 09, 2018 07:02 AM HST
expected result Monday, Jul 09, 2018 08:02 PM HST
actual results: Monday, Jul 09, 2018 08:02 PM EEST
or Monday, Jul 09, 2018 07:02 AM HST
tl;dr
You are seeing a feature, not a bug, adjusting same moment to another time zone.
8 PM in eastern Europe is also 7 AM in Hawaii.
Use real time zone names, not 3-4 letter pseudo zones.
Use modern java.time classes, not poorly-designed legacy classes.
Same moment: Call withZoneSameInstant
You adjusted from one time zone 3 hours ahead of UTC to another zone 10 hours behind UTC, for a total difference of 13 hours. Understand that 8 PM in one place is simultaneously 7 AM in the other.
Note our call to withZoneSameInstant (‘Instant’) in the following code.
ZonedDateTime.of(
2018 , 7 , 9 , 20 , 2 , 0 , 0 , ZoneId.of( "Europe/Athens" ) // 8 PM in Greece.
)
.withZoneSameInstant( // Adjust into another time zone to view the same moment with another wall-clock time.
ZoneId.of( "Pacific/Honolulu" ) // 7 AM in Hawaii.
)
.toString()
2018-07-09T07:02-10:00[Pacific/Honolulu]
Different moment: Call withZoneSameLocal
Apparently you wanted a different moment, a different point on the timeline, that has the same date and same time-of-day but a different time zone.
Note our call to withZoneSameLocal (‘Local’, not ‘Instant’) in the following code.
ZonedDateTime.of(
2018 , 7 , 9 , 20 , 2 , 0 , 0 , ZoneId.of( "Europe/Athens" ) // 8 PM in Greece.
)
.withZoneSameLocal( // Different moment, coincidentally having the same date and same time-of-day. But different time zone means this is a different point on the timeline.
ZoneId.of( "Pacific/Honolulu" ) // Also 8 PM in Hawaii, which happens many hours later than 8 PM in Greece. Different moment, same wall-clock time.
)
.toString()
2018-07-09T20:02-10:00[Pacific/Honolulu]
Details
Real time zone
HST & EEST are pseudo-zones, not real time zones. Avoid these 3-4 letter codes as they are not standardized and are not even unique(!).
Use real time zone names as defined in tzdata by the IANA. See a list in Wikipedia (possibly outdated). These names are in Continent/Region format such as America/Montreal or Europe/Tallinn.
ZoneId z = ZoneId.of( "Europe/Vilnius" ) ;
Avoid legacy date-time classes
Avoid the terribly troublesome classes Calendar & SimpleDateFormat. These were supplanted years ago by the java.time classes. Working with java.time is much clearer and easier.
Let's get your starting point. I am guessing that by EEST you had in mind one of the eastern European time zones. I am choosing one arbitrarily.
ZoneId zAthens = ZoneId.of( "Europe/Athens" ) ;
LocalDate ld = LocalDate.of( 2018 , Month.JULY , 9 ) ; // 2018-07-09.
LocalTime lt = LocalTime.of( 20 , 2 ) ; // 8:02 PM.
ZonedDateTime zdtAthens = ZonedDateTime.of( ld , lt , zAthens ) ;
Generate a String representing the value of that ZonedDateTime object. By default, use standard ISO 8601 format wisely extended to append the name of the time zone in square brackets.
String outputAthens = zdtAthens.toString() ; // Generate `String` in a format extending standard ISO 8601 format.
2018-07-09T20:02+03:00[Europe/Athens]
By HST I guess you mean Hawaii time. The proper name for that zone is Pacific/Honolulu.
ZoneId zHonolulu = ZoneId.of( "Pacific/Honolulu" ) ;
Let's adjust our Athens moment into this other zone for Hawaii. Same moment, same point on the timeline, but a different wall-clock time. Imagine a pair of friends in each place calling each and simultaneously looking up at a clock on their wall. Each sees a different time-of-day and possibly a different date, nevertheless they experience the same simultaneous moment, same point on the timeline.
ZonedDateTime zdtHonolulu = zdtAthens.withZoneSameInstant( zHonolulu ) ; // Same moment (same `Instant` inside the `ZonedDateTime`) but a different time zone.
On that date, Honolulu is ten hours behind UTC while Athens is three hours ahead. That is a total delta of thirteen hours. So, 8 PM (20:00) minus 13 is 7 AM. We expect to see 7 AM in Hawaii. Let's verify, by generating another string in ISO 8601 format.
String outputHonolulu = zdtHonolulu.toString() ; // Generate `String` representing the value of the `ZonedDateTime` object.
2018-07-09T07:02-10:00[Pacific/Honolulu]
Sure enough, 7 AM.
Perhaps what you wanted was the same date and same time-of-day located in Hawaii. This would mean you are not representing the same simultaneous moment. You would be representing a different point on the timeline, off by several hours.
The ZonedDateTime does provide for this function. Call ZonedDateTime::withZoneSameLocal meaning conceptually: Use the same internal LocalDate and the same internal LocalTime, but use a different assigned ZoneId.
ZonedDateTime eightPmOnJuly9InPacificHonolulu = zdtAthens.withZoneSameLocal( zHonolulu) ;
String outputDifferentMoment= eightPmOnJuly9InPacificHonolulu.toString() ;
2018-07-09T20:02-10:00[Pacific/Honolulu]
UTC
All this flipping around between time zones can drive a person batty. Get grounded by focusing on UTC. Think of UTC as The One True Time, and all other zones are but mere variations.
To adjust from a time zone to UTC, extract a Instant object from our ZonedDateTime objects. An Instant is always in UTC by definition.
Instant instantAthens = zdtAthens.toInstant() ;
Instant instantHonolulu = zdtHonolulu.toInstant() ;
Instant instantDifferentMoment = eightPmOnJuly9InPacificHonolulu.toInstant() ;
2018-07-09T17:02:00Z
2018-07-09T17:02:00Z
2018-07-10T06:02:00Z
The Z on the end means UTC, is pronounced Zulu, and is defined by ISO 8601 and other standards.
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, and later
Built-in.
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
Much 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.
As I understand device.getUpdatedDate() returns a java.sql.Timestamp, for example 2018-07-09 20:02:26.506000. I assume that (contrary to the idea of a timestamp) this date and time was produced without time zone, but that you know that it is in Pacific/Honolulu time zone (Hawaii Standard Time or HST, used in Hawaii all year).
If you are getting the Timestamp from an SQL database (as would be normal) — don’t. The Timestamnp class is long outdated and quite confusing. Assuming you are using at least Java 8 and at least JDBC 4.2, you may get an instance of the modern LocalDateTime from the database instead. Assuming that rs is your result set and your column is named updated_date (I trust you to tailor the code to your situation):
DateTimeFormatter formatter
= DateTimeFormatter.ofPattern(EMAIL_DATE_FORMAT, Locale.US);
LocalDateTime updatedDateTime
= rs.getObject("updated_date", LocalDateTime.class);
ZonedDateTime timeOnHawaii
= updatedDateTime.atZone(ZoneId.of("Pacific/Honolulu"));
String formattedDateTime = timeOnHawaii.format(formatter);
If your timestamp is stored in the database with time zone information you can do even better by using Instant.class instead and be sure to get the same point in time as in the database. The exact possibilities depend on the capabilities of your JDBC driver.
In case you cannot avoid getting the Timestamp, convert it like this:
ZonedDateTime timeOnHawaii = device.getUpdatedDate()
.toLocalDateTime()
.atZone(ZoneId.of("Pacific/Honolulu"));
With your example Timestamp and the formatter from my first code snippet above this prints:
Monday, Jul 09, 2018 08:02 PM HST
(assuming that EMAIL_DATE_FORMAT is defined as "EEEE, MMM dd, yyyy hh:mm a zzz").
What went wrong in your code?
One confusing thing about Timestamp is that it represents a point in time (without time zone) but has often been used for representing a date and time of day (also without time zone) instead. So your Timestamp of 2018-07-09 20:02:26.506000 really holds the value (point in time) equal to 2018-07-09 20:02:26.506000 Eastern European Summer Time (EEST, as used in Украина/Ukraine) but is confusingly used by device.getUpdatedDate() to mean 2018-07-09 20:02:26.506000 in Hawaii. Neither Date nor SimpleDateFormat change the point in time, which is why they give you Monday, Jul 09, 2018 08:02 PM EEST or the equivalent Monday, Jul 09, 2018 07:02 AM HST.
Links:
More about the confusion of Timestamp in my answer to this question: Java - Convert java.time.Instant to java.sql.Timestamp without Zone offset
Oracle tutorial: Date Time explaining how to use java.time.
I'm sending date from the Angular app as String to server and converting to java Date object to store in the database.
Also sending timeZoneOffset from UI to use the client's time zone while converting. (After googling I found this approach to get the proper result based on the user location)
Written the following code to convert:
public static void main(String args[]) throws ParseException {
String inputDate = "04/05/2018"; // This date coming from UI
int timeZoneOffset = -330; // This offset coming from UI
// (new Date().getTimeZoneOffset())
getDate(inputDate, timeZoneOffset);
}
public static Date getDate(String inputDate, int timeZoneOffset)
throws ParseException {
SimpleDateFormat dateFormat = new SimpleDateFormat("MM/dd/yyyy");
ZoneOffset zoneOffset = ZoneOffset.ofTotalSeconds(-timeZoneOffset * 60);
System.out.println("Default time zone: " + TimeZone.getDefault().getID());
TimeZone timeZone = TimeZone.getTimeZone(zoneOffset);
System.out.println("Time zone from offset: " + timeZone.getID());
dateFormat.setTimeZone(timeZone);
Date date = dateFormat.parse(inputDate);
System.out.println("Converted date: " + date);
return date;
}
Expected output:
Default time zone: America/New_York
Time zone from offset: GMT+05:30
Converted date: Thu April 5 00:00:00 IST 2018
Actual result in server:
Default time zone: America/New_York
Time zone from offset: GMT+05:30
Converted date: Wed April 4 14:30:00 EDT 2018
Why is the date decreasing to one day even I set the users time zone? I'm new to Date and Time related concepts and I googled a couple of times didn't find answer, could someone please help on this.
Thanks in advance
The Answer by Godfrey is correct.
tl;dr
LocalDate.parse(
"04/05/2018" ,
DateTimeFormatter.ofPattern( "MM/dd/uuuu" )
)
.atStartOfDay(
ZoneId.of( "Asia/Kolkata" )
)
.toString()
2018-04-05T00:00+05:30[Asia/Kolkata]
For storage in your database, use UTC.
When a new day starts in India, the date at UTC is still “yesterday”, so April 4th rather than April 5th. Same moment, same point on the timeline, different wall-clock time.
LocalDate.parse(
"04/05/2018" ,
DateTimeFormatter.ofPattern( "MM/dd/uuuu" )
)
.atStartOfDay(
ZoneId.of( "Asia/Kolkata" )
)
.toInstant()
2018-04-04T18:30:00Z
java.time
You are using terrible old date-time classes that have proven to be poorly designed, confusing, and troublesome. They are now supplanted by the java.time classes.
Avoid legacy date-time classes entirely
ZoneOffset zoneOffset = ZoneOffset.ofTotalSeconds(-timeZoneOffset * 60);
…
TimeZone timeZone = TimeZone.getTimeZone(zoneOffset);
You are mixing the modern classes (ZoneOffset) with the troublesome legacy classes (TimeZone). Do not mix the modern classes with the legacy classes. Forget all about the old classes including Date, Calendar, and SimpleDateFormat. The java.time classes are designed to entirely supplant the legacy classes.
Instead of TimeZone, use ZoneId (and ZoneOffset).
LocalDate
Parse your input string as a LocalDate. The LocalDate class represents a date-only value without time-of-day and without time zone.
String input = "04/05/2018" ;
DateTimeFormatter f = DateTimeFormatter.ofPattern( "MM/dd/uuuu" ) ;
LocalDate ld = LocalDate.parse( input , f ) ;
Offset versus Time Zone
int timeZoneOffset = -330;
An offset-from-UTC is not a time zone. An offset is simply a number of hours, minutes, and seconds of displacement from UTC. Your choice of variable name indicates possible confusion on this point.
ZoneOffset offset = ZoneOffset.of( -3 , 30 ) ;
A time zone is a history of past, present, and future changes in offset used by the people of a particular region. So a time zone is always preferable to an offset.
Specify a proper time zone name in the format of continent/region, such as America/Montreal, Africa/Casablanca, or Pacific/Auckland. 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(!).
ZoneId z = ZoneId.of( "Asia/Kolkata" ) ; // India time zone. Currently uses offset of +05:30 (five and a half hours ahead of UTC).
First moment of the day
You seem to be aiming for the first moment of that date in that zone. Let java.time determine that first-moment-of-the-day. Do not assume that time is 00:00:00. In some zones on some dates, the day may start at another time such as 01:00:00.
ZonedDateTime zdt = ld.atStartOfDay( z ) ; // Determine the first moment of the day on this date in this zone. Not always 00:00:00.
As an example of why you should be using time zones rather than mere offset-from-UTC, look at your example data of -330 which I might easily misinterpret to be three and a half hours behind UTC. This offset is currently only used in the zone America/St_Johns, and only used there for part of the year. So if you applied an offset of -03:30 to a date in the wrong part of the year, your results would be invalid yet go undetected.
Using offset (not recommended)
But your example lacks time zone, so let’s go with offset-from-UTC rather than zone.
Your use of an int integer number to represent an offset-from-UTC is a poor choice of types. First of all, it is ambiguous. That -330 might be interpreted to be a clumsy attempt at -03:30 offset of three and a half hours behind schedule. Secondly, it makes parsing trickier than need be. Thirdly, as a number of minutes, it ignores the possibility of an offset with seconds. Fourthly, you use a negative number for an offset ahead of UTC (apparently) despite common usage and standard usage being the opposite. Lastly, it ignores the clear standard set by ISO 8601 for representing offsets as text: ±HH:MM:SS (and variations). By the way, the padding zero is optional in the standard, but I recommend always including because various libraries and protocols expect it.
Your intent appears to be a number of minutes intended by the integer number.
long seconds =( TimeUnit.MINUTES.toSeconds( - 330 ) * -1 ); // Multiply by negative one to flip the sign to standard ISO 8601 usage, where `+` means “ahead* of UTC and `-` means *behind* UTC.
seconds: 19800
ZoneOffset offset = ZoneOffset.ofTotalSeconds( ( int ) seconds );
offset.toString(): +05:30
Last step: get the first moment of the day in this offset. Caveat: We do not know for certain if this offset is valid on this date, as we lack a time zone.
Convert from the returned ZonedDateTime to an OffsetDateTime. As discussed above, determining first moment of day should always be done with a time zone, and thereby get a ZonedDateTime. We are violating that sensible practice to use an offset, but using the returned ZonedDateTime object would be misleading as ours would lack a true time zone, and have only a mere offset. So the OffsetDateTime class makes our intentions clear and our code more self-documenting.
OffsetDateTime odt = ld.atStartOfDay( offset ).toOffsetDateTime();
Again, this approach using offset is not recommending, as you should be instead gathering a time zone name from the user as input rather than an offset.
UTC
Generally best to store moments in UTC.
Extract a Instant from your OffsetDateTime or ZonedDateTime to get the same moment as UTC.
Instant instant = zdt.toInstant() ;
2018-04-04T18:30:00Z
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, and later
Built-in.
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
Much 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.
It's not decreasing by one day, it is decreasing by 11.5 hours. That happens to be the time difference between GMT+05:30 and "America/New_York", which is GMT-04:30 or GMT-05:30 (depending on time of year).
GMT+05:30 is somewhere in India, I think, since that is about the only place to use a 30 minute offset rather than a whole hour. When it is April 5th in India, it is still April 4th in New York.
The problem may be you aren't getting a time from the client, so it will assume midnight. If you are doing time zone conversion, it is best to include the actual time.
I am trying to convert IST to UTC epoch in Java
But instead of subtracting 5.30 hours from IST, it adds 5.30 in IST
public static long convertDateToEpochFormat(String date) {
Date convertedDate = null;
try {
LOGGER.info(date);
DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
formatter.setTimeZone(TimeZone.getTimeZone("UTC"));
LOGGER.info(date);
convertedDate = formatter.parse(date);
LOGGER.info(convertedDate);
} catch (ParseException e) {
e.printStackTrace();
}
return convertedDate.getTime() / 1000L;
}
The log statements I obtained is :
2017-01-01 00:00:00
2017-01-01 00:00:00
Sun Jan 01 05:30:00 IST 2017
It should ideally be Dec 31 18:30:00 because of UTC conversion.
Can anyone tell me whats wrong ?
tl;dr
Why does util.Date forwards the date instead of subtracting it?
Because India time is ahead of UTC, not behind.
Instant.parse(
"2017-01-01 00:00:00".replace( " " , "T" ) + "Z"
).atZone(
ZoneId.of( "Asia/Kolkata" )
).toString()
2017-01-01T05:30+05:30[Asia/Kolkata]
Using java.time
You are using troublesome old date-time classes that are now legacy, supplanted by the java.time classes.
ISO 8601
Your input string is almost in standard ISO 8601 format. To comply fully, replace that SPACE in the middle with a T. The java.time classes use standard formats when parsing/generating strings. So no need to specify a formatting pattern.
String input = "2017-01-01 00:00:00".replace( " " , "T" ) ;
If that input is meant to represent a moment in UTC, append a Z, short for Zulu, means UTC.
String input = "2017-01-01 00:00:00".replace( " " , "T" ) + "Z" ; // Assuming this input was intended to be in UTC.
2017-01-01T00:00:00Z
When possible, just use the ISO 8601 formats in the first place when serializing date-time values to strings.
Instant
Parse that input string as an Instant, a moment on the timeline in UTC with a resolution of nanoseconds.
Instant instant = Instant.parse( input ) ;
instant.toString(): 2017-01-01T00:00:00Z
ZonedDateTime
You seem to want this value adjusted into India time. Apply a ZoneId to get a ZonedDateTime.
Specify a proper time zone name in the format of continent/region, such as America/Montreal, Africa/Casablanca, or Pacific/Auckland. 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(!).
ZoneId z = ZoneId.of( "Asia/Kolkata" ) ;
ZonedDateTime zdt = instant.atZone( z ) ;
zdt.toString(): 2017-01-01T05:30+05:30[Asia/Kolkata]
See this code run live at IdeOne.com.
India time is ahead of UTC
Your Question expects the India time to go backwards, behind the UTC value. This makes no sense. India time is ahead of UTC, not behind UTC. The Americas have time zones behind UTC as they lay westward. East of the Prime Meridian in Greenwich are offsets ahead of UTC. In modern times, ISO 8601 and most other protocols mark such offsets with a plus sign: +05:30. Note that some old protocols did the opposite (used a negative sign).
Midnight UTC = 5:30 AM India
So midnight in UTC, 00:00:00 at the Prime Meridian, is simultaneously five-thirty in the morning in India.
So all three of these represent the same simultaneous moment, the same point in the timeline:
2017-01-01T00:00:00Z
2017-01-01T05:30+05:30[Asia/Kolkata]
2016-12-31T16:00-08:00[America/Los_Angeles]
Avoid count-from-epoch
Do not handle time as an integer count from epoch as you are doing by returning a long from your method as seen in the Question. In your Java code pass around date-time values using date-time objects, java.time objects specifically. When passing date-time values outside your Java code, serialize to strings using the practical ISO 8601 formats.
Relying on an integer count-from-epoch values is confusing, difficult to debug, impossible to read by humans, and will lead to frustration and errors (even worse: unobserved errors).
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.
Where to obtain the java.time classes?
Java SE 8, Java SE 9, and later
Built-in.
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
Much of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport.
Android
The ThreeTenABP project adapts ThreeTen-Backport (mentioned above) for Android specifically.
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.
The answer by Basil Bourque is not only correct, it is also very informative. I have already upvoted it. I’ll try just a little bit of a different angle.
As I understand your question, your date-time string 2017-01-01 00:00:00 should be interpreted in IST, AKA Asia/Kolkata time, and you want to convert it to seconds (not milliseconds) since the epoch. And you are asking why you are getting an incorrect result.
I think the answer is rather banal: When the date-time string is in India time, you should not set UTC time on the formatter you use for parsing it. This is sure to get an incorrect result (if you were to format the date-time into UTC, you would do well in setting UTC as time zone on the formatter used for formatting, but this is a different story).
I agree with Basil Bourque that you should avoid the outdated classes Date and SimpleDateFormat. So here’s my suggestion (assuming you do need epoch seconds and cannot use an Instant as Basil Bourque recommends).
private static DateTimeFormatter parseFormatter = DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss");
public static long convertDateToEpochFormat(String date) {
return LocalDateTime.parse(date, parseFormatter)
.atZone(ZoneId.of("Asia/Kolkata"))
.toInstant()
.getEpochSecond();
}
This will convert your example string into an instant of 2016-12-31T18:30:00Z and return 1483209000. Please check for yourself that it is correct.
I have been assuming all the way that by IST you meant Indian Standard Time. Please be aware that three and four letter time zone abbreviations are ambiguous. For example, my JVM thinks that IST means Israel Standard Time. If you intended the same, please substitute Asia/Jerusalem for Asia/Kolkata. If you meant Irish Standard Time (another recognized/semi-official interpretation), please use Europe/Dublin. You will of course get different output in each case.
I understand that java Date is timezoneless and trying to set different timezone on Java Calendar wouldn't convert date to an appropriate Time Zone. So I have tried following code
public static String DATE_FORMAT="dd MMM yyyy hh:mm:ss";
public static String CURRENT_DATE_STRING ="31 October 2011 14:19:56 GMT";
DateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT);
dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
System.out.println(dateFormat.parseObject(CURRENT_DATE_STRING));
but it outputs wrong date Mon Oct 31 16:19:56 when it must be 12:19:56?
The main issue here is your date format string is using hh (12-hour clock) instead of HH (24-hour)
Secondly, your date format should specify that your date string contains the timezone.
(Alternatively you could uncomment the commented line, to tell it the correct timezone).
Thirdly, you should use a DateFormat to output the time to screen aswell...
Finally, UTC = GMT, so the UTC time is also 14:19:56
(GMT, 'British Winter Time', is the same as UTC, whereas BST is one hour ahead)
public class DateFormatTest {
public static String DATE_FORMAT="dd MMM yyyy HH:mm:ss z";
public static String CURRENT_DATE_STRING ="31 October 2011 14:19:56 GMT";
public static void main(String[] args) throws ParseException {
DateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT);
//dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
Date d= dateFormat.parse(CURRENT_DATE_STRING);
dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
System.out.println(dateFormat.format(d));
}
}
Output: 31 Oct 2011 14:19:56 UTC
HTH
tl;dr
On that date, at that time, in some eastern Europe time zones, the clocks were running two hours ahead of UTC. So the hour of 14 in UTC (GMT) will appear as 16 (not 12) in zones such as Europe/Helsinki.
ZonedDateTime
.parse
(
"31 October 2011 14:19:56 GMT" ,
DateTimeFormatter.ofPattern
(
"dd MMMM uuuu HH:mm:ss z" ,
Locale.US
)
)
.withZoneSameInstant
(
ZoneId.of( "Europe/Helsinki" )
)
.toString()
2011-10-31T16:19:56+02:00[Europe/Helsinki]
java.time
I understand that java Date is timezoneless
Actually, a java.util.Date represents a moment as seen in UTC, an offset of zero hours-minutes-seconds.
Beware of Date::toString. That terrible toString method dynamically applies the JVM’s current default time zone while generating its text. This creates an illusion of that time zone having been part of the object. One of many reasons to never use this class.
trying to set different timezone on Java Calendar wouldn't convert date to an appropriate Time Zone
You should be using the modern java.time classes, never Calendar. Specifically, use ZonedDateTime to represent a moment as seen through the wall-clock time used by the people of a particular region (a time zone).
"31 October 2011 14:19:56 GMT";
Your input of "31 October 2011 14:19:56 GMT" does not match your formatting pattern "dd MMM yyyy hh:mm:ss". That pattern fails to account for the offset of your input, an offset of zero hours-minutes-seconds indicated by the GMT at the end.
Firstly, do not exchange date-time values using such formats. Learn to use ISO 8601 standard formats for exchanging date-time values as text. The java.time classes conveniently use these standard formats by default when parsing/generating text, so no need to specify a pattern at all.
But if you must parse that particular input string of yours, define a formatting pattern to match.
String input = "31 October 2011 14:19:56 GMT";
Locale locale = Locale.US;
DateTimeFormatter f = DateTimeFormatter.ofPattern( "dd MMMM uuuu HH:mm:ss z" , locale );
ZonedDateTime zdt = ZonedDateTime.parse( input , f );
Notice how we specified a Locale, to determine the human language and cultural norms used in translation of your input.
EEST – Eastern European Summer Time
Apparently you want to view this moment as seen in the time zone of eastern Europe. I will arbitrarily choose one of the several time zones in that area.
Be aware that EEST is not a real time zone. 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 EST or IST or EEST as they are not true time zones, not standardized, and not even unique(!).
ZoneId zoneHelsinki = ZoneId.of( "Europe/Helsinki" );
ZonedDateTime zdtHelsinki = zdt.withZoneSameInstant( zoneHelsinki );
Dump to console.
System.out.println( "zdt = " + zdt );
System.out.println( "zdtHelsinki = " + zdtHelsinki );
zdt = 2011-10-31T14:19:56Z[GMT]
zdtHelsinki = 2011-10-31T16:19:56+02:00[Europe/Helsinki]
Notice how the hour changed from 14 to 16 because at that moment the clocks in Finland were running two hours ahead of UTC.
but it outputs wrong date Mon Oct 31 16:19:56 when it must be 12:19:56?
No the hour 14 is in UTC. Eastern Europe runs ahead of UTC, not behind it. As seen above, Finland on that day was running two hours ahead, so the little hand on the clocks of Finland were pointing at 4 (16 hour) rather than 12.
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.
To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.
The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.
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.
Use Joda Time. It's recommended by many StackOverflow users and is well documented with examples on timezone conversion.
Good luck!
What's the whole output? Date.toString() should print time zone. Maybe it's not in UTC in your case.