First, I want to state that I know the Java Calendar class is being supplanted by other libraries that are arguably better. Perhaps I've stumbled upon one of the reasons Calendar has fallen out of favor.
I ran into frustrating behavior in Calendar as it regards to the overlapping hour at the end of daylight savings time.
public void annoying_issue()
{
Calendar midnightPDT = Calendar.getInstance(TimeZone.getTimeZone("US/Pacific"));
midnightPDT.set(Calendar.YEAR, 2021);
midnightPDT.set(Calendar.MONTH, 10);
midnightPDT.set(Calendar.DAY_OF_MONTH, 7);
midnightPDT.set(Calendar.HOUR_OF_DAY, 0);
midnightPDT.set(Calendar.MINUTE, 0);
midnightPDT.set(Calendar.SECOND, 0);
midnightPDT.set(Calendar.MILLISECOND, 0);
Calendar oneAMPDT = Calendar.getInstance(TimeZone.getTimeZone("US/Pacific"));
oneAMPDT.setTimeInMillis(midnightPDT.getTimeInMillis() + (60*60*1000));//this is the easiest way I've found to get to the first 1am hour at DST overlap
System.out.println(new Date(midnightPDT.getTimeInMillis()));//prints the expected "Sun Nov 7 00:00:00 PDT 2021"
System.out.println(new Date(oneAMPDT.getTimeInMillis()));//prints "Sun Nov 7 01:00:00 PDT 2021" also expected
oneAMPDT.clear(Calendar.MINUTE);//minute is already 0 so no change should occur... RIGHT!?
//WRONG!!!!
//The time is now in PST! The millisecond value has increased by 3600000, too!!
System.out.println(new Date(oneAMPDT.getTimeInMillis()));//prints "Sun Nov 7 01:00:00 PST 2021"
}
Following along with the comments you'll see that clearing the MINUTE field in the calendar actually moved it up an hour! The HECK!?
This also occurs when I use oneAMPDT.set(Calendar.MINUTE, 0)
Is this expected behavior? Is there a way to prevent this?
Avoid legacy date-time classes; convert if needed
As you noted, Calendar was supplanted years ago by the java.time classes defined in JSR 310 (unanimously adopted). And as you note there are many reasons to avoid using Calendar & Date etc.
If you must have a Calendar object to interoperate with old code not yet updated to java.time, convert after doing your work in java.time.
java.time
Specify your desired time zone. Note that US/Pacific is merely an alias for the actual time zone, America/Los_Angeles.
ZoneId zLosAngeles = ZoneId.of( "America/Los_Angeles" ) ;
Specify your desired moment.
LocalDate ld = LocalDate.of( 2021 , Month.NOVEMBER , 7 ) ;
In your code, you seem to assume the first moment of the day occurs at 00:00. That is not always the case. Some dates in some time zones may start at another time. So let java.time determine the first moment of the day.
ZonedDateTime firstMomentOfThe7thInLosAngeles = ld.atStartOfDay( zLosAngeles ) ;
firstMomentOfThe7thInLosAngeles.toString(): 2021-11-07T00:00-07:00[America/Los_Angeles]
But then you jumped to another moment, to 1 AM.
ZonedDateTime oneAmOnThe7thLosAngeles = firstMomentOfThe7thInLosAngeles.with( LocalTime.of( 1 , 0 ) ) ;
oneAmOnThe7thLosAngeles.toString(): 2021-11-07T01:00-07:00[America/Los_Angeles]
That time-of-day may or may not exist on that date in that zone. The ZonedDateTime class will adjust if need be.
You used the name midnightPDT for a variable. I suggest avoiding the term midnight as its use confuses date-time handling with out a precise definition. I recommend using the term "first moment of the day" if that is what you mean.
You extract a count of milliseconds since the epoch reference of first moment of 1970 as seen in UTC, 1970-01-01T00:00Z.
Instant firstMomentOfThe7thInLosAngelesAsSeenInUtc = firstMomentOfThe7thInLosAngeles.toInstant() ;
long millisSinceEpoch_FirstMomentOf7thLosAngeles = firstMomentOfThe7thInLosAngelesAsSeenInUtc.toEpochMilli() ;
firstMomentOfThe7thInLosAngelesAsSeenInUtc.toString(): 2021-11-07T07:00:00Z
millisSinceEpoch_FirstMomentOf7thLosAngeles = 1636268400000
And you do the same for our 1 AM moment.
Instant oneAmOnThe7thLosAngelesAsSeenInUtc = oneAmOnThe7thLosAngeles.toInstant() ;
long millisSinceEpoch_OneAmOn7thLosAngeles = oneAmOnThe7thLosAngelesAsSeenInUtc.toEpochMilli() ;
oneAmOnThe7thLosAngelesAsSeenInUtc.toString(): 2021-11-07T08:00:00Z
millisSinceEpoch_OneAmOn7thLosAngeles = 1636272000000
We should see a difference of one hour. An hour = 3,600,000 = 60 * 60 * 1,000.
long diff = ( millisSinceEpoch_OneAmOn7thLosAngeles - millisSinceEpoch_FirstMomentOf7thLosAngeles ); // 3,600,000 = 60 * 60 * 1,000.
diff = 3600000
Cutover
Then you go on to mention the Daylight Saving Time (DST) cutover. The cutover for DST in the United States on that date was 2 AM, not 1 AM. At the moment of 2 AM arriving, the clocks swung back to 1 AM, for a second 1:00-2:00 AM hour.
To get to that point of cutover, let's add an hour.
ZonedDateTime cutover_Addition = oneAmOnThe7thLosAngeles.plusHours( 1 );
cutover_Addition = 2021-11-07T01:00-08:00[America/Los_Angeles]
Notice that the time-of-day shows the same (1 AM), but the offset-from-UTC has changed from being 7 hours behind UTC to now 8 hours behind UTC. There lies the hour difference you seek.
Let's get the count of milliseconds since epoch for this third moment. Before we had first moment of the day (00:00), then the first occurring 1 AM, and now we have the second occurring 1 AM on this “Fall-Back” date of November 7, 2021.
long millisSinceEpoch_Cutover = cutover_Addition.toInstant().toEpochMilli();
1636275600000
Duration.between( firstMomentOfThe7thInLosAngelesAsSeenInUtc , cutover_Addition.toInstant() ) = PT2H
Duration.between( oneAmOnThe7thLosAngelesAsSeenInUtc , cutover_Addition.toInstant() ) = PT1H
The ZonedDateTime class does offer a pair of methods of use at these moments of cutover: withEarlierOffsetAtOverlap and withLaterOffsetAtOverlap.
ZonedDateTime cutover_OverlapEarlier =
cutover_Addition
.withEarlierOffsetAtOverlap();
ZonedDateTime cutover_OverlapLater =
cutover_Addition
.withLaterOffsetAtOverlap();
cutover_OverlapEarlier = 2021-11-07T01:00-07:00[America/Los_Angeles]
cutover_OverlapLater = 2021-11-07T01:00-08:00[America/Los_Angeles]
Calendar
If you really need a Calendar object, just convert.
Calendar x = GregorianCalendar.from( firstMomentOfThe7thInLosAngeles ) ;
Calendar y = GregorianCalendar.from( oneAmOnThe7thLosAngeles ) ;
Calendar z = GregorianCalendar.from( cutover_Addition );
If you goal is simply struggling with understanding Calendar class behavior, I suggest you stop the masochism. There is no point. Sun, Oracle, and the JCP community all gave up on those terrible legacy date-time classes. I suggest you do the same.
Example code
Pulling together all that code above.
ZoneId zLosAngeles = ZoneId.of( "America/Los_Angeles" );
LocalDate ld = LocalDate.of( 2021 , Month.NOVEMBER , 7 );
ZonedDateTime firstMomentOfThe7thInLosAngeles = ld.atStartOfDay( zLosAngeles );
ZonedDateTime oneAmOnThe7thLosAngeles = firstMomentOfThe7thInLosAngeles.with( LocalTime.of( 1 , 0 ) );
Instant firstMomentOfThe7thInLosAngelesAsSeenInUtc = firstMomentOfThe7thInLosAngeles.toInstant();
long millisSinceEpoch_FirstMomentOf7thLosAngeles = firstMomentOfThe7thInLosAngelesAsSeenInUtc.toEpochMilli();
Instant oneAmOnThe7thLosAngelesAsSeenInUtc = oneAmOnThe7thLosAngeles.toInstant();
long millisSinceEpoch_OneAmOn7thLosAngeles = oneAmOnThe7thLosAngelesAsSeenInUtc.toEpochMilli();
long diff = ( millisSinceEpoch_OneAmOn7thLosAngeles - millisSinceEpoch_FirstMomentOf7thLosAngeles ); // 3,600,000 = 60 * 60 * 1,000.
ZonedDateTime cutover_Addition = oneAmOnThe7thLosAngeles.plusHours( 1 );
long millisSinceEpoch_Cutover = cutover_Addition.toInstant().toEpochMilli();
ZonedDateTime cutover_OverlapEarlier =
cutover_Addition
.withEarlierOffsetAtOverlap();
ZonedDateTime cutover_OverlapLater =
cutover_Addition
.withLaterOffsetAtOverlap();
Convert to legacy classes, if need be.
Calendar x = GregorianCalendar.from( firstMomentOfThe7thInLosAngeles );
Calendar y = GregorianCalendar.from( oneAmOnThe7thLosAngeles );
Calendar z = GregorianCalendar.from( cutover_Addition );
Dump to console.
System.out.println( "firstMomentOfThe7thInLosAngeles = " + firstMomentOfThe7thInLosAngeles );
System.out.println( "oneAmOnThe7thLosAngeles = " + oneAmOnThe7thLosAngeles );
System.out.println( "firstMomentOfThe7thInLosAngelesAsSeenInUtc = " + firstMomentOfThe7thInLosAngelesAsSeenInUtc );
System.out.println( "millisSinceEpoch_FirstMomentOf7thLosAngeles = " + millisSinceEpoch_FirstMomentOf7thLosAngeles );
System.out.println( "oneAmOnThe7thLosAngelesAsSeenInUtc = " + oneAmOnThe7thLosAngelesAsSeenInUtc );
System.out.println( "millisSinceEpoch_OneAmOn7thLosAngeles = " + millisSinceEpoch_OneAmOn7thLosAngeles );
System.out.println( "diff = " + diff );
System.out.println( "x = " + x );
System.out.println( "y = " + y );
System.out.println( "z = " + z );
System.out.println( "cutover_Addition = " + cutover_Addition );
System.out.println( "millisSinceEpoch_Cutover = " + millisSinceEpoch_Cutover );
System.out.println( "Duration.between( firstMomentOfThe7thInLosAngelesAsSeenInUtc , cutover_Addition.toInstant() ) = " + Duration.between( firstMomentOfThe7thInLosAngelesAsSeenInUtc , cutover_Addition.toInstant() ) );
System.out.println( "Duration.between( oneAmOnThe7thLosAngelesAsSeenInUtc , cutover_Addition.toInstant() ) = " + Duration.between( oneAmOnThe7thLosAngelesAsSeenInUtc , cutover_Addition.toInstant() ) );
System.out.println( "cutover_OverlapEarlier = " + cutover_OverlapEarlier );
System.out.println( "cutover_OverlapLater = " + cutover_OverlapLater );
When run.
firstMomentOfThe7thInLosAngeles = 2021-11-07T00:00-07:00[America/Los_Angeles]
oneAmOnThe7thLosAngeles = 2021-11-07T01:00-07:00[America/Los_Angeles]
firstMomentOfThe7thInLosAngelesAsSeenInUtc = 2021-11-07T07:00:00Z
millisSinceEpoch_FirstMomentOf7thLosAngeles = 1636268400000
oneAmOnThe7thLosAngelesAsSeenInUtc = 2021-11-07T08:00:00Z
millisSinceEpoch_OneAmOn7thLosAngeles = 1636272000000
diff = 3600000
x = java.util.GregorianCalendar[time=1636268400000,areFieldsSet=true,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="America/Los_Angeles",offset=-28800000,dstSavings=3600000,useDaylight=true,transitions=185,lastRule=java.util.SimpleTimeZone[id=America/Los_Angeles,offset=-28800000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=3,startMonth=2,startDay=8,startDayOfWeek=1,startTime=7200000,startTimeMode=0,endMode=3,endMonth=10,endDay=1,endDayOfWeek=1,endTime=7200000,endTimeMode=0]],firstDayOfWeek=2,minimalDaysInFirstWeek=4,ERA=1,YEAR=2021,MONTH=10,WEEK_OF_YEAR=44,WEEK_OF_MONTH=1,DAY_OF_MONTH=7,DAY_OF_YEAR=311,DAY_OF_WEEK=1,DAY_OF_WEEK_IN_MONTH=1,AM_PM=0,HOUR=0,HOUR_OF_DAY=0,MINUTE=0,SECOND=0,MILLISECOND=0,ZONE_OFFSET=-28800000,DST_OFFSET=3600000]
y = java.util.GregorianCalendar[time=1636272000000,areFieldsSet=true,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="America/Los_Angeles",offset=-28800000,dstSavings=3600000,useDaylight=true,transitions=185,lastRule=java.util.SimpleTimeZone[id=America/Los_Angeles,offset=-28800000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=3,startMonth=2,startDay=8,startDayOfWeek=1,startTime=7200000,startTimeMode=0,endMode=3,endMonth=10,endDay=1,endDayOfWeek=1,endTime=7200000,endTimeMode=0]],firstDayOfWeek=2,minimalDaysInFirstWeek=4,ERA=1,YEAR=2021,MONTH=10,WEEK_OF_YEAR=44,WEEK_OF_MONTH=1,DAY_OF_MONTH=7,DAY_OF_YEAR=311,DAY_OF_WEEK=1,DAY_OF_WEEK_IN_MONTH=1,AM_PM=0,HOUR=1,HOUR_OF_DAY=1,MINUTE=0,SECOND=0,MILLISECOND=0,ZONE_OFFSET=-28800000,DST_OFFSET=3600000]
z = java.util.GregorianCalendar[time=1636275600000,areFieldsSet=true,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="America/Los_Angeles",offset=-28800000,dstSavings=3600000,useDaylight=true,transitions=185,lastRule=java.util.SimpleTimeZone[id=America/Los_Angeles,offset=-28800000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=3,startMonth=2,startDay=8,startDayOfWeek=1,startTime=7200000,startTimeMode=0,endMode=3,endMonth=10,endDay=1,endDayOfWeek=1,endTime=7200000,endTimeMode=0]],firstDayOfWeek=2,minimalDaysInFirstWeek=4,ERA=1,YEAR=2021,MONTH=10,WEEK_OF_YEAR=44,WEEK_OF_MONTH=1,DAY_OF_MONTH=7,DAY_OF_YEAR=311,DAY_OF_WEEK=1,DAY_OF_WEEK_IN_MONTH=1,AM_PM=0,HOUR=1,HOUR_OF_DAY=1,MINUTE=0,SECOND=0,MILLISECOND=0,ZONE_OFFSET=-28800000,DST_OFFSET=0]
cutover_Addition = 2021-11-07T01:00-08:00[America/Los_Angeles]
millisSinceEpoch_Cutover = 1636275600000
Duration.between( firstMomentOfThe7thInLosAngelesAsSeenInUtc , cutover_Addition.toInstant() ) = PT2H
Duration.between( oneAmOnThe7thLosAngelesAsSeenInUtc , cutover_Addition.toInstant() ) = PT1H
cutover_OverlapEarlier = 2021-11-07T01:00-07:00[America/Los_Angeles]
cutover_OverlapLater = 2021-11-07T01:00-08:00[America/Los_Angeles]
java.time
Is this expected behavior? No. I consider it a bug.
Is there a way to prevent this? Yes, the way you already mentioned or at least implied: use ZonedDateTime instead of Calendar. Basil Bourque has said it already. As a modest supplement I wanted to show the full round-trip from Calendar to ZonedDateTime, setting minute to 0 and converting back to Calendar. In case you need this for interoperability with your legacy code.
GregorianCalendar oneAmPdt = new GregorianCalendar(TimeZone.getTimeZone(ZoneId.of("America/Los_Angeles")));
oneAmPdt.clear();
oneAmPdt.set(2021, Calendar.NOVEMBER, 7, 0, 0);
oneAmPdt.add(Calendar.HOUR_OF_DAY, 1);
System.out.println(oneAmPdt.getTime());
ZonedDateTime zdt = oneAmPdt.toZonedDateTime();
// Minute is already 0 so no change should occur... RIGHT!?
zdt = zdt.withMinute(0);
oneAmPdt = GregorianCalendar.from(zdt);
System.out.println(oneAmPdt.getTime());
Output:
Sun Nov 07 01:00:00 PDT 2021
Sun Nov 07 01:00:00 PDT 2021
But I used GregorianCalendar, not Calendar? So did you. GregorianCalendar is the subclass of Calendar that you got from Calendar.getIntance(). In some environments you would have got a different subclass reflecting the calendar system in use there, and your initial calls to set would not have given you your expected result. You want a GregorianCalendar in this case (if you cannot have a ZonedDateTime from the outset).
When modifying our old code I would likely do it in the above way even if it wasn’t for circumventing a bug in the old Calendar or GregorianCalendar class. It’s one small step in a long-running transition to java.time.
I have write a static class named"DateUtils" and here is a static method named "parseDate(String)" it will use some patterns to convert the string to a date.
the default timeZone is Asia/Shanghai (I'am in China)
public static final TimeZone SHA = TimeZone.getTimeZone("Asia/Shanghai");
and it will be passed to the method
TimeZone.setDefault(timeZone);
And Using SimpleDateFormat to concert the String.
The matched pattern should be "EEE, dd MMM yyyy HH:mm:ss zzz"
And Here are three Test of it.
//this one is ok.
#Test
public void testParseGMTDate() throws Exception {
Date date = DateUtils.parseDate("Thu, 02 Aug 2016 08:12:34 GMT");
assertNotNull(date);
Calendar cal = Calendar.getInstance();
cal.setTime(date);
log.debug(DateUtils.format(cal.getTime()));
assertEquals(cal.get(Calendar.YEAR), 2016);
assertEquals(cal.get(Calendar.MONTH), 8 - 1);
assertEquals(cal.get(Calendar.DATE), 2);
assertEquals(cal.get(Calendar.HOUR_OF_DAY), 8 + 8);
assertEquals(cal.get(Calendar.MINUTE), 12);
assertEquals(cal.get(Calendar.SECOND), 34);
assertEquals(DateUtils.format(cal.getTime()), "2016-08-02 16:12:34");
}
// AWST is GMS+8:00 time so this one is ok.
#Test
public void testParseWSTDate() throws Exception {
Date date = DateUtils.parseDate("Thu, 02 Aug 2016 08:12:34 AWST");
assertNotNull(date);
Calendar cal = Calendar.getInstance();
cal.setTime(date);
log.debug(DateUtils.format(cal.getTime()));
assertEquals(cal.get(Calendar.YEAR), 2016);
assertEquals(cal.get(Calendar.MONTH), 8 - 1);
assertEquals(cal.get(Calendar.DATE), 2);
assertEquals(cal.get(Calendar.HOUR_OF_DAY), 8);
assertEquals(cal.get(Calendar.MINUTE), 12);
assertEquals(cal.get(Calendar.SECOND), 34);
}
// junit.framework.AssertionFailedError:
// Expected :9
// Actual :8
#Test
public void testParseDateCCT() throws Exception {
Date date = DateUtils.parseDate("Thu, 02 Aug 2016 08:12:34 CCT");
assertNotNull(date);
Calendar cal = Calendar.getInstance();
cal.setTime(date);
log.debug(DateUtils.format(cal.getTime()));
assertEquals(cal.get(Calendar.YEAR), 2016);
assertEquals(cal.get(Calendar.MONTH), 8 - 1);
assertEquals(cal.get(Calendar.DATE), 2);
assertEquals(cal.get(Calendar.HOUR_OF_DAY), 8); //Expected: 9
assertEquals(cal.get(Calendar.MINUTE), 12);
assertEquals(cal.get(Calendar.SECOND), 34);
}
And here are some code snippet of DateUtils.
public static final TimeZone SHA = TimeZone.getTimeZone("Asia/Shanghai");
static {
Calendar calendar = Calendar.getInstance();
calendar.setTimeZone(SHA);
calendar.set(2000, 0, 1, 0, 0, 0);
calendar.set(MILLISECOND, 0);
TWO_DIGIT_START = calendar.getTime();
}
public static Date parseDate(String dateValue) {
return parseDate(dateValue, null, null, SHA);
}
public static Date parseDate(String dateValue, String[] dateFormats, Date startDate, TimeZone timeZone) {
TimeZone.setDefault(timeZone);
String[] localDateFormats = dateFormats != null ? dateFormats : DEFAULT_PATTERNS;
Date localStartDate = startDate != null ? startDate : TWO_DIGIT_START;
String v = dateValue;
if (dateValue.length() > 1 && dateValue.startsWith("\'") && dateValue.endsWith("\'")) {
v = dateValue.substring(1, dateValue.length() - 1);
}
String[] arr = localDateFormats;
int len = localDateFormats.length;
for (String dateFormat : DEFAULT_PATTERNS) {
// String dateFormat = arr[i];
SimpleDateFormat dateParser = DateUtils.DateFormatHolder.formatFor(dateFormat);
dateParser.set2DigitYearStart(localStartDate);
ParsePosition pos = new ParsePosition(0);
Date result = dateParser.parse(v, pos);
if (pos.getIndex() != 0) {
_LOG.debug("Date parsed using: {}", dateFormat);
return result;
}
}
_LOG.error("Can't parse data: {data:{}, formats:{}, startDate:{},tz:{}}",
dateValue, localDateFormats, localStartDate, TimeZone.getDefault());
return null;
}
My Question is:
According to document on the internet, CCT & AWST are both the GMT+8:00 time zone, why in my test it seem like that the CCT Time is GMT+9:00.
tl;dr
You have made three mistakes:
Used non-standard non-unique pseudo-time-zone 3-4 letter abbreviations
Then proceeded to incorrectly assume their meaning
Used old outmoded Java classes for date-time handling.
Instead, use proper time zone names in the format of continent/region. Study the documented meaning of those zones rather than assume/guess. Use only java.time classes for date-time work.
All these are common errors made by many programmers, as evidenced by the many Questions here on Stack Overflow.
Use proper time zone names
CST is also Central Standard Time in North America. One example of why you should never use these 3-4 letter abbreviations. They are not true time zones, not standardized, and not even unique(!). Use proper IANA time zone names. These are in the format of continent/region.
java.time
You are using old troublesome date-time classes. Avoid them.
The java.time framework is built into Java 8 and later. These classes supplant the old troublesome date-time classes such as java.util.Date, .Calendar, & java.text.SimpleDateFormat. The Joda-Time team also advises migration to java.time.
To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations.
Much of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport and further adapted to Android in ThreeTenABP.
Instant
The Instant class is a moment on the timeline in UTC (GMT) with a resolution up to nanoseconds.
Instant instant = Instant.parse ( "2016-08-02T08:12:34Z" ); // 02 Aug 2016 08:12:34
ZonedDateTime
The ZonedDateTime represents an Instant adjusted into a time zone.
I assume you meant Australian western standard time by AWST, officiall named Australia/Perth.
ZoneId zoneId_Perth = ZoneId.of ( "Australia/Perth" );
ZonedDateTime zdt_Perth = instant.atZone ( zoneId_Perth );
For China, you intended Asia/Shanghai.
ZoneId zoneId_Shanghai = ZoneId.of ( "Asia/Shanghai" );
ZonedDateTime zdt_Shanghai = zdt_Perth.withZoneSameInstant ( zoneId_Shanghai );
I am not sure what your or the old date-time classes meant by CCT. This page says it is “Cocos Islands Time”, six and a half hours ahead of UTC year-round (no Daylight Saving Time, DST). Again, this is an example of why you should never use the 3-4 letter abbreviations.
ZoneId zoneId_Cocos = ZoneId.of ( "Indian/Cocos" );
ZonedDateTime zdt_Cocos = zdt_Perth.withZoneSameInstant ( zoneId_Cocos );
You may have meant Beijing time, according to a comment. If so, know that Beijing time is covered by the Asia/Shanghai time zone according to this list in Wikipedia.
Dump to console. You can see that both Perth and Shanghai are eight hours ahead of UTC in August this year for an hour-of-day of 16 versus 8 (16:12:34 versus 08:12:34). The Cocos Islands is in between, at 14:42:34 for 6.5 hours ahead of UTC.
System.out.println ( "instant: " + instant + " | zdt_Perth: " + zdt_Perth + " | zdt_Shanghai: " + zdt_Shanghai + " | zdt_Cocos: " + zdt_Cocos );
instant: 2016-08-02T08:12:34Z | zdt_Perth: 2016-08-02T16:12:34+08:00[Australia/Perth] | zdt_Shanghai: 2016-08-02T16:12:34+08:00[Asia/Shanghai] | zdt_Cocos: 2016-08-02T14:42:34+06:30[Indian/Cocos]
DateTimeFormatter
To generate strings in formats other than the standard ISO 8601 formats shown here, use DateTimeFormatter class.
DateTimeFormatter f = DateTimeFormatter.ofLocalizedDateTime( FormatStyle.FULL );
f = f.withLocale( Locale.CHINA );
String output = zdt_Shanghai.format( f );
I'm trying to convert the date time string 10/10/2015 10:00:00 to the seconds since the unix epoch, which is 1444471200. However, weirdly I'm getting back the value 1444467600 which is actually 10/10/2015 09:00:00. Here's my code:
// using "joda-time" % "joda-time" % "2.9",
// "org.joda" % "joda-convert" % "1.8.1",
import org.joda.time.DateTime
import org.joda.time.format.DateTimeFormat
val dt = DateTime.parse(dateTimeString, DateTimeFormat.forPattern("MM/dd/yyyy HH:mm:ss"))
dt.getMillis / 1000
Where's this hour offset coming from and how can I get rid of it? My local timezone is GMT (which == UTC at the moment anyway)...
Apparently, it's not parsing with GMT/UTC. Just add that to your DateTimeFormat.
DateTimeFormat.forPattern("MM/dd/yyyy HH:mm:ss").withZoneUTC()
java.time
The Answer by Sotirios Delimanolis is correct. Here is the same kind of code for the java.time framework built into Java 8 and later, intended as the successor to Joda-Time.
First we define a formatter. Notice the call-chain, calling withZone to specify the zone by which we want the string parsed. This is not optional, as the input string lacks offset or time zone data.
Then we call the static parse method on ZonedDateTime to actually do the parsing.
Lastly we call the convenience method toEpochSecond to convert this date-time to the number of seconds from the epoch of 1970-01-01T00:00:00Z.
String input = "10/10/2015 10:00:00";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern ( "MM/dd/yyyy HH:mm:ss" ).withZone ( ZoneOffset.UTC );
ZonedDateTime zdt = ZonedDateTime.parse ( input , formatter );
long secondsFromEpoch = zdt.toEpochSecond ();
Dump to console.
System.out.println ( "input: " + input + " | zdt: " + zdt + " | secondsFromEpoch: " + secondsFromEpoch );
input: 10/10/2015 10:00:00 | zdt: 2015-10-10T10:00Z | secondsFromEpoch: 1444471200
I try to parse a String and set a time zone, but I can't produce the desired result.
String dtc = "2014-04-02T07:59:02.111Z";
SimpleDateFormat readDate = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
Date date = null;
try {
date = readDate.parse(dtc);
Log.d("myLog", "date "+date);
} catch (ParseException e) {
Log.d("myLog", "dateExcep " + e);
}
SimpleDateFormat writeDate = new SimpleDateFormat("dd.MM.yyyy, HH.mm");
writeDate.setTimeZone(TimeZone.getTimeZone("GMT+04:00"));
String dateString = writeDate.format(date);
At the output of the variable "dateString" still gives the time 07:59:02 , and I want to make it +4 hours in advance that is 11:59:02
You need to instruct the read-formatter to interprete the input as UTC (GMT - remember that Z stands for UTC in ISO-8601-format):
String dtc = "2014-04-02T07:59:02.111Z";
SimpleDateFormat readDate = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
readDate.setTimeZone(TimeZone.getTimeZone("GMT")); // missing line
Date date = readDate.parse(dtc);
SimpleDateFormat writeDate = new SimpleDateFormat("dd.MM.yyyy, HH.mm");
writeDate.setTimeZone(TimeZone.getTimeZone("GMT+04:00"));
String s = writeDate.format(date);
Then you will get:
02.04.2014, 11.59
Joda-Time
Doing date-time work much easier and simpler with the Joda-Time library than with the notoriously troublesome bundled java.util.Date & .Calendar classes.
Time Zone
Using proper time zone names, rather than a specific offset, is generally a wiser approach.
Example Code
Here is some example code using Joda-Time 2.3.
Notice:
Joda-Time has built-in parsers for strings in ISO 8601 format. No need to instantiate parsing or formatting objects.
Joda-Time is both parsing the UTC string and adjusting it to another time zone all in one call to the DateTime constructor.
When converting to a UTC-based dateTime in the last line, we still have the same moment in the timeline of the Universe (same count of milliseconds since Unix epoch).
Source…
String input = "2014-04-02T07:59:02.111Z";
DateTimeZone timeZone = DateTimeZone.forID( "Asia/Dubai" );
DateTime dateTimeDubai = new DateTime( input, timeZone ); (a) Parse, (b) Adjust time zone.
DateTime dateTimeUtc = dateTimeDubai.withZone( DateTimeZone.UTC );
Dump to console…
System.out.println( "input: " + input );
System.out.println( "dateTimeDubai: " + dateTimeDubai );
System.out.println( "dateTimeUtc: " + dateTimeUtc );
When run…
input: 2014-04-02T07:59:02.111Z
dateTimeDubai: 2014-04-02T11:59:02.111+04:00
dateTimeUtc: 2014-04-02T07:59:02.111Z
you can take whatever pattern you want like i have date+ time zone. so you can set according to you. here is my code.
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
here i have set the date format.
String currentDate = dateFormat.format(currentLocalTime);
String localTime = date.format(currentLocalTime);// to get the time zone i have used this.
String newtime=currentDate+localTime;// and then i have apeend this now print log you will get the desire result.
I am trying to convert between a date printed out in an EST timezone into a date printed out in GMT/UTC
package com.stefano;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
public class MainEntry {
/**
* #param args
* #throws ParseException
*/
public static void main(String[] args) throws ParseException {
String dateTime = "1307011200"; //12:00PM 01 July 2013
System.out.println("Input -> " + dateTime);
SimpleDateFormat format = new SimpleDateFormat("yyMMddHHmm");
format.setTimeZone(TimeZone.getTimeZone("EST"));
Date date = format.parse(dateTime);
System.out.println("Intermediate -> " + date);
format.setTimeZone(TimeZone.getTimeZone("GMT"));
System.out.println("Output -> " + format.format(date));
}
}
The output it gives is:
Input -> 1307011200
Intermediate -> Mon Jul 01 17:00:00 BST 2013
Output -> 1307011600
Even though the time difference between EST and GMT is always 5, it is somehow getting involved with BST.
I cannot use Joda-Time.
The javadoc of the SimpleDateFormat.parse(String) method refers to the parse(String, ParsePosition) method, that says:
This parsing operation uses the calendar to produce a Date. As a result, the calendar's date-time fields and the TimeZone value may have been overwritten, depending on subclass implementations. Any TimeZone value that has previously been set by a call to setTimeZone may need to be restored for further operations.
According to this you can't use this method to tell the SimpleDateFormat which timezone
the given date is in.
You can fix this method like this:
String dateTime = "1307011200"; // 12:00PM 01 July 2013
dateTime += " EST"; // append the timezone information to the input string
System.out.println("Input -> " + dateTime);
SimpleDateFormat format = new SimpleDateFormat("yyMMddHHmm z"); // tell the formatter to look for the timezone info
Date date = format.parse(dateTime);
System.out.println("Intermediate -> " + date);
format.setTimeZone(TimeZone.getTimeZone("GMT"));
System.out.println("Output -> " + format.format(date));
This will also print the Date object using your local timezone, but it shows a way to parse the dateTime string using a given timezone.
The answer by zovits is correct.
US East Coast Offset
If by EST you mean the east coast of the United States (and parts of Canada), then your statement the time difference between EST and GMT is always 5 is incorrect. With Daylight Saving Time (DST), the offset may be -05:00 or -04:00. Indeed, your specified date-time does have DST in effect.
Avoid 3-4 Letter Time Zone Codes
Those three or four letter time zone codes are neither standardized nor unique. Avoid them. Use proper time zone names, most of which are continent+city.
Comparison To Joda-Time
For comparison, here is some Joda-Time example code. The java.util.Date & .Calendar classes bundled with Java are so notoriously troublesome that every Java programmer should move to either Joda-Time or the new Java 8 java.time package (inspired by Joda-Time, defined by JSR 310).
While a java.util.Date seems to have a time zone but actually does not, note that a Joda-Time DateTime does truly know its own assigned time zone.
Joda-Time uses the ISO 8601 standard for its defaults. You can use other formats as well, as seen with the Montréal example below.
Example Code
String input = "1307011200"; //12:00PM 01 July 2013
DateTimeFormatter formatterSmooshed = DateTimeFormat.forPattern( "yyMMddHHmm" );
DateTimeZone timeZoneNewYork = DateTimeZone.forID( "America/New_York" );
DateTime dateTimeNewYork = formatterSmooshed.withZone( timeZoneNewYork ).parseDateTime( input );
DateTime dateTimeUtc = dateTimeNewYork.withZone( DateTimeZone.UTC );
String outputMontréal = DateTimeFormat.forStyle( "FF" ).withLocale( Locale.CANADA_FRENCH ).print( dateTimeNewYork );
String outputSmooshed = formatterSmooshed.print( dateTimeNewYork ); // Expect same as input.
Dump to console…
System.out.println( "input: " + input );
System.out.println( "dateTimeNewYork: " + dateTimeNewYork );
System.out.println( "dateTimeUtc: " + dateTimeUtc );
System.out.println( "outputMontréal: " + outputMontréal );
System.out.println( "outputSmooshed: " + outputSmooshed );
When run…
input: 1307011200
dateTimeNewYork: 2013-07-01T12:00:00.000-04:00
dateTimeUtc: 2013-07-01T16:00:00.000Z
outputMontréal: lundi 1 juillet 2013 12 h 00 EDT
outputSmooshed: 1307011200