as a part of my requirement,I have to fire a SQL query which takes yesterday's midnight and today's midnight in the respective time zone as input.Is there a way to achieve this?
Use ZonedDateTime:
String DATE_FORMAT = "dd-M-yyyy hh:mm:ss a";
String dateInString = "22-1-2015 10:15:55 AM";
LocalDateTime ldt = LocalDateTime.parse(dateInString, DateTimeFormatter.ofPattern(DATE_FORMAT));
ZoneId singaporeZoneId = ZoneId.of("Asia/Singapore");
System.out.println("TimeZone : " + singaporeZoneId);
//LocalDateTime + ZoneId = ZonedDateTime
ZonedDateTime asiaZonedDateTime = ldt.atZone(singaporeZoneId);
System.out.println("Date (Singapore) : " + asiaZonedDateTime);
ZoneId newYokZoneId = ZoneId.of("America/New_York");
System.out.println("TimeZone : " + newYokZoneId);
ZonedDateTime nyDateTime = asiaZonedDateTime.withZoneSameInstant(newYokZoneId);
System.out.println("Date (New York) : " + nyDateTime);
DateTimeFormatter format = DateTimeFormatter.ofPattern(DATE_FORMAT);
System.out.println("\n---DateTimeFormatter---");
System.out.println("Date (Singapore) : " + format.format(asiaZonedDateTime));
System.out.println("Date (New York) : " + format.format(nyDateTime));
Output is:
TimeZone : Asia/Singapore
Date (Singapore) : 2015-01-22T10:15:55+08:00[Asia/Singapore]
TimeZone : America/New_York
Date (New York) : 2015-01-21T21:15:55-05:00[America/New_York]
---DateTimeFormatter---
Date (Singapore) : 22-1-2015 10:15:55 AM
Date (New York) : 21-1-2015 09:15:55 PM
Use the methods from here to get what you need
Example taken from: Java – Convert date and time between timezone
simply run your cronjob at every hour and generate only the reports in which timezones the day just ended
java.time
Use a driver compliant with JDBC 4.2 or later, running on Java 8 or later, to benefit from use of the modern java.time classes that supplant the troublesome old legacy date-time classes.
Determining "today" and "yesterday" means determining a date. Determining a date requires a time zone. For any given moment, the date varies around the globe by zone.
ZoneId z = ZoneId.of( "America/Montreal" ) ;
LocalDate today = LocalDate.now( z ) ;
LocalDate yesterday = today.minusDays( 1 ) ;
To query for timestamps in the database, we need specific moments. The Question specifies midnight. The term "midnight" is vague. Let's use "first moment of the day" instead.
Never assume the day starts at 00:00:00. Anomalies such as Daylight Saving Time (DST) mean it may start at another time such as 01:00:00. Let java.time determine first moment of the day.
ZonedDateTime start = yesterday.atStartOfDay( z ) ;
Generally, the best approach to defining a span of time is though the Half-Open approach where the beginning is inclusive while the ending is exclusive. So we want to start at first moment of one day and run up to, but not include, the first moment of the next day.
ZonedDateTime stop = today.atStartOfDay( z ) ;
In Half-Open, we do not use the SQL command BETWEEN.
SQL
SELECT * FROM t
WHERE event >= ? AND event < ? ;
Java
myPreparedStatement.setObject( 1 , start ) ;
myPreparedStatement.setObject( 2 , stop ) ;
To retrieve the timestamps, use getObject.
Instant instant = myResultSet.getObject( "event" , Instant.class ) ;
To move from UTC to a zone, apply a ZoneId.
ZonedDateTime zdt = instant.atZone( z ) ;
Related
Hi I've already search for similar questions but without luck.
I'm calling a ws that sends me back a token and when it's valid example:
{
"token": ...,
"notBefore":"Thu 21 Jul 2022 at 10:50:43",
"notOnOrAfter":"Thu 21 Jul 2022 at 12:50:43"
}
I know that this dates are GMT+2 (Rome), now I'm taking the current time and convert the two strings:
ZonedDateTime currentTime = LocalDateTime.now().atZone(ZoneId.of("GMT+2"));
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(token.getTimePattern(), Locale.ENGLISH);
ZonedDateTime tokenNotValidAfter = LocalDateTime.parse(token.getNotOnOrAfter(), dateTimeFormatter).atZone(ZoneId.of("GMT+2"));
ZonedDateTime tokenNotValidBefore = LocalDateTime.parse(token.getNotBefore(), dateTimeFormatter).atZone(ZoneId.of("GMT+2"));
if (!currentTime.isAfter(tokenNotValidBefore) || !currentTime.isBefore(tokenNotValidAfter)) {
throw new CifTokenExpiredException(ExceptionHandlerConfig.CIF_TOKEN_EXPIRED);
}
Now locally everthing is working fine, when i deploy on cloud i get:
ZonedDateTime currentTime = LocalDateTime.now().atZone(ZoneId.of("GMT+2"));
two hours behind.
How can i solve this without adding two hours to currentTime? (doing like this locally will not work)
Regards
GMT+2 is an offset, Middle Europe +1 hour plus 1 hour summer time.
That would go wrong in the coming winter. In fact you are using the incomplete OffsetDateTime, which without Locale is inconclusive for the real Zone.
ZonedDateTime currentTime = ZonedDateTime.now(ZoneId.of("Europe/Rome"));
As you see, no need to revert to LocalDateTime.
Now you can get currentTime.toInstant() for an absolute UTC number.
An Instant is even Comparable, but Before/After is fine.
You assume that before and after limits are also in the Italian time zone, but that may be as it is.
There are some things to consider, because of DST (summer time, daylight savings time):
With a count down ("Still 243 hours till the end") you can get awkward jumps of 1 hour per day.
Twice a year there is the 02:00-03:00 gap or repetition (Western Europe). These stati can be queried.
The answer by Joop Eggen is correct. I'll add some more complete code example.
I suggest you educate the publisher of your data on two points:
Date-time values being exchanged textually should use standard ISO 8601 format rather than inventing a localized format as seen in your case.
Moments (specific points on the timeline) should always be communicated in the context of a time zone or offset. Preferably with an offset of zero hours-minutes-seconds. This implied time zone of Rome is just asking for trouble. Or, if not a moment but a time-of-day that should remain the same even if politicians change the time zone rules, then communicate the intended time zone as another field in the date stream, in Continent/Region format such as Europe/Rome.
I suggest adding the ThreeTen-Extra library to your project. Doing so gives you the Interval class that represents a span of time attached to the timeline as a pair of Instant objects. Handy methods include contains, abuts, overlaps, and more.
String inputBegin = "Thu 21 Jul 2022 at 10:50:43";
String inputEnd = "Thu 21 Jul 2022 at 12:50:43";
Locale locale = Locale.US;
DateTimeFormatter f = DateTimeFormatter.ofPattern( "EEE d MMM uuuu 'at' HH:mm:ss" ).withLocale( locale );
ZoneId zoneId = ZoneId.of( "Europe/Rome" ); // Assumed to be the intended time zone.
LocalDateTime beginLdt = LocalDateTime.parse( inputBegin , f );
LocalDateTime endLdt = LocalDateTime.parse( inputEnd , f );
ZonedDateTime begin = beginLdt.atZone( zoneId );
ZonedDateTime end = endLdt.atZone( zoneId );
Interval whenValid = Interval.of( begin.toInstant() , end.toInstant() );
Instant now = Instant.now();
boolean isValidNow = whenValid.contains( now );
System.out.println( begin + "/" + end );
String message = "Interval: " + whenValid + " contains now: " + now + " = " + isValidNow;
System.out.println( message );
2022-07-21T10:50:43+02:00[Europe/Rome]/2022-07-21T12:50:43+02:00[Europe/Rome]
Interval: 2022-07-21T08:50:43Z/2022-07-21T10:50:43Z contains now: 2022-07-21T21:17:54.458095Z = false
In the cloud time is always considered to be GMT. So the best action is to change ZonedDateTime currentTime = LocalDateTime.now().atZone(ZoneId.of("GMT+2"));
to
ZonedDateTime currentTime = ZonedDateTime.now()
I had a timestamp in Unix with milliseconds, now I need the start of the day and end of the day with milliseconds for the same given timestamp.
Eg: 1637293359000 - Given timestamp
Start of the day to be 1637280000000
End of the day to be 1637323199000
In System default timezone you could try like this
ZoneId zoneId = ZoneId.systemDefault();
long timestamp = 1637293359000L;
LocalDate date = Instant.ofEpochMilli(timestamp).atZone(zoneId).toLocalDate();
LocalDateTime startDay = date.atStartOfDay();
LocalDateTime endDay = date.atTime(LocalTime.MAX);
System.out.println(startDay);
System.out.println(endDay);
long startDayLongValue = startDay.atZone(zoneId).toInstant().toEpochMilli();
long endDayLongValue = endDay.atZone(zoneId).toInstant().toEpochMilli();
System.out.println(startDayLongValue);
System.out.println(endDayLongValue);
We can achieve this using DateTime
import org.joda.time.DateTime;
long timestamp = 1629454215381L;
DateTime dateTime=new DateTime(timestamp );
long StartOfDayMillis = dateTime.withMillis(System.currentTimeMillis()).withTimeAtStartOfDay().getMillis();
long EndOfDayMillis = dateTime.withMillis(StartOfDayMillis).plusDays(1).minusSeconds(1).getMillis();
tl;dr
Here is the complete code to a record representing the day of a specified moment as seen in a particular time zone. A static factory method contains our logic.
package work.basil.example;
import java.time.*;
import java.util.Objects;
public record DayInMillis( long start , long end )
{
public static DayInMillis from ( final long countOfMillisSinceEpoch , final ZoneId zoneId )
{
Objects.requireNonNull( zoneId );
Instant instant = Instant.ofEpochMilli( countOfMillisSinceEpoch );
ZonedDateTime zdt = instant.atZone( zoneId );
LocalDate ld = zdt.toLocalDate();
ZonedDateTime start = ld.atStartOfDay( zoneId );
ZonedDateTime end = ld.plusDays( 1 ).atStartOfDay( zoneId );
Instant startInstant = start.toInstant();
Instant endInstant = end.toInstant();
long startMilli = startInstant.toEpochMilli();
long endMilli = endInstant.toEpochMilli();
System.out.println( "instant = " + instant );
System.out.println( "zdt = " + zdt );
System.out.println( "start/end = " + start + "/" + end );
System.out.println( "startInstant/endInstant = " + startInstant + "/" + endInstant );
System.out.println( "startMilli/endMilli = " + startMilli + "/" + endMilli );
System.out.println( "Duration (not necessarily 24 hours): " + Duration.between( startInstant , endInstant ) );
return new DayInMillis( startMilli , endMilli );
}
}
Example usage:
DayInMillis.from( 1_637_293_359_000L , ZoneId.of( "Asia/Tokyo" ) )
When run.
instant = 2021-11-19T03:42:39Z
zdt = 2021-11-19T12:42:39+09:00[Asia/Tokyo]
start/end = 2021-11-19T00:00+09:00[Asia/Tokyo]/2021-11-20T00:00+09:00[Asia/Tokyo]
startInstant/endInstant = 2021-11-18T15:00:00Z/2021-11-19T15:00:00Z
startMilli/endMilli = 1637247600000/1637334000000
Duration (not necessarily 24 hours): PT24H
dayInMillis = DayInMillis[start=1637247600000, end=1637334000000]
Parse count of seconds
Parse your input count of milliseconds since epoch of first moment of 1970 in UTC as a Instant object.
Instant instant = Instant.ofEpochMilli( 1_637_293_359_000L ) ;
Adjust into time zone
Specify the time zone by which you want to perceive dates.
ZoneId z = ZoneId.of( "America/Edmonton" ) ;
Adjust from UTC (an offset of zero hours-minutes-seconds) to that time zone.
ZonedDateTime zdt = instant.atZone( z ) ;
Get the date
Extract the date-only portion.
LocalDate ld = zdt.toLocalDate() ;
Start of day
Let java.time determine the first moment of the day. Never assume the day starts at 00:00. Some dates in some zones may start at another time such as 01:00.
ZonedDateTime start = ld.atStartOfDay( z ) ;
Half-Open
The last moment of the day is infinitely divisible. For this and other reasons, spans of time are usually best defined using Half-Open approach. In Half-Open, the beginning is inclusive while the ending is exclusive. This means the span of a day starts at the first moment of the day and runs up to, but does not include, the first moment of the following day.
ZonedDateTime end = ld.plusDays( 1 ).atStartOfDay( z ) ;
Now we have the span covered by a pair of ZonedDateTime objects. But you want to get a count of milliseconds since epoch for both of those.
Tracking count-from-epoch is awkward
Let me say, I do not recommend using a count-since-epoch for time-keeping. The values are inherently ambiguous as to both epoch and granularity. Furthermore, using such counts makes debugging difficult and errors hard to spot, as such values are meaningless to human readers.
ISO 8601
Instead, I suggest communicating such values textually in standard ISO 8601 format. The java.time classes use ISO 8601 formats by default when parsing/generating text.
String outputStart = start.toInstant().toString();
String outputEnd = end.toInstant().toString();
Milliseconds since epoch
But if you insist on the count of milliseconds, first extract Instant objects from our ZonedDateTime objects, effectively adjusting to UTC (offset of zero).
Instant startInstant = start.toInstant() ;
Instant endInstant = end.toInstant() ;
Interrogate for a count of milliseconds since epoch.
long startMilli = startInstant.toEpochMilli() ;
long endMilli = endInstant.toEpochMilli() ;
Avoid LocalDateTime for this problem
Notice that at no point did we use the LocalDateTime class. That class purposely lacks the context of a time zone or offset-from-UTC. So LocalDateTime cannot represent a moment, is not a point on the timeline. That makes LocalDateTime irrelevant to the problem at hand.
Tip: ThreeTen-Extra library
If you routinely work with pairs of moments, considering adding the ThreeTen-Extra library to your project. This provides the Interval class for representing a pair of Instant objects. The class carries several handy comparison methods such as abuts, overlaps, contains, and so on.
I want to get the UTC instant (since my DB is storing in UTC) from Java (which is also in UTC) of a particular time zone, this is what I have tried so far:
public static Instant getStartOfTheDayDateTime(Instant instant, String zoneId) {
ZonedDateTime zoned = instant.atZone(ZONE_ID_TO_ZONE_MAP.get(zoneId));
ZoneId zone = ZoneId.of(zoneId);
return zoned.toLocalDate().atStartOfDay(zone).toInstant();
// ZonedDateTime startOfTheDay = zoned.withHour(0)
// .withMinute(0)
// .withSecond(0)
// .withNano(0);
//
// return startOfTheDay.toInstant();
}
public static Instant getEndOfTheDayDateTime(Instant instant, String zoneId) {
ZonedDateTime zoned = instant.atZone(ZONE_ID_TO_ZONE_MAP.get(zoneId));
ZonedDateTime endOfTheDay = zoned.withHour(0)
.withMinute(0)
.withSecond(0)
.withNano(0)
.plusDays(1);
return endOfTheDay.toInstant();
}
Every attempt shows:
2020-04-10 22:00:00.0(Timestamp), 2020-04-11 22:00:00.0(Timestamp)
Is this the start/end of the day UTC time in Europe/Paris zone ?
I was expecting to have 2020-04-11 02:00:00.0(Timestamp), 2020-04-12 02:00:00.0(Timestamp)
Right now, Paris is on summer time: UTC+2. Paris is 'ahead' of UTC by 2 hours.
So 00:00:00 in Paris local time is 22:00:00 UTC.
Is this the start/end of the day UTC time in Europe/Paris zone ?
Yes. Europe/Paris is in daylight savings time. Midnight in Paris occurred at 22:00 UTC time.
I was expecting to have 2020-04-11 02:00:00.0(Timestamp), 2020-04-12 02:00:00.0(Timestamp)
That's not right, 02:00 UTC would have been 04:00 in Paris time.
Ask programmatically if a moment is in DST
Is this the start/end of the day UTC time in Europe/Paris zone ?
Get start of day.
ZoneId z = ZoneId.of( "Europe/Paris" );
ZonedDateTime zdtStartOfDay = instant.atZone( z ).toLocalDate().atStartOfDay( z ) ;
Ask if that moment is in DST for that zone.
ZoneRules rules = z.getRules();
boolean isDst = rules.isDaylightSavings( zdtStartOfDay.toInstant() );
Pass date-time objects rather than mere strings
public static Instant getStartOfTheDayDateTime(Instant instant, String zoneId)
I suggest you ask the calling programmer to pass a valid ZoneId object rather than a mere string. It should not be the job of this method to validate their string input. If it is reasonable to expect a Instant then it is also reasonable to expect a ZoneId.
public static Instant getStartOfTheDayDateTime(Instant instant, ZoneID zoneId )
Half-Open
public static Instant getEndOfTheDayDateTime(Instant instant, String zoneId) {
Trying to determine the last moment of the day is impossible because of infinitely divisible last second.
Also this approach to defining a span of time is awkward. It makes abutting multiple spans tricky. Various software systems and protocols differ in their resolution of that last fractional second, using milliseconds, microseconds, nanoseconds, or some other fraction.
The common practice in date-time handling is to track a span of time using the Half-Open approach. In Half-Open, the beginning is inclusive while the ending is exclusive.
So a full day begins with the first moment of the day and runs up to, but does not include, the first moment of the next day.
ZoneId z = ZoneId.of( "Europe/Paris" );
ZonedDateTime zdtStartOfDay = instant.atZone( z ).toLocalDate().atStartOfDay( z ) ;
ZonedDateTime zdtStartOfNextDay = instant.atZone( z ).toLocalDate().plusDays( 1 ).atStartOfDay( z ) ;
You might want to break that code out to more lines, for easier reading/debugging.
Instant instant = Instant.now() ; // Or passed in.
ZoneId z = ZoneId.of( "Europe/Paris" ) ;
ZonedDateTime zdt = instant.atZone( z ) ;
LocalDate ld = zdt.toLocalDate() ;
LocalDate ldNextDay = ld.plusDays( 1 ) ;
ZonedDateTime zdtStartOfNextDay = ldNextDay.atStartOfDay( z ) ;
See this code run live at IdeOne.com. For example:
System.out.println( instant ) ; // 2020-04-13T00:15:25.235341Z
System.out.println( zdt ) ; // 2020-04-13T02:15:25.235341+02:00[Europe/Paris]
System.out.println( ld ) ; // 2020-04-13
System.out.println( ldNextDay ) ; // 2020-04-14
System.out.println( zdtStartOfNextDay ) ; // 2020-04-14T00:00+02:00[Europe/Paris]
ThreeTen-Extra Interval
If you do this kind of work with spans of time often, then I suggest adding the ThreeTen-Extra library to your project. That library includes the Interval class to track a span-of-time as a a pair of Instant objects.
Interval interval = Interval.of( zdtStartOfDay.toInstant() , zdtStartOfNextDay.toInstant() ) ;
You can then make use the several handy comparison methods such as abuts, contains, encloses, intersection, overlaps, and union.
Timestamp
Never use the java.sql.Timestamp class. This class is part of the terrible date-time classes that shipped with the earliest versions of Java. These classes are now legacy, supplanted entirely by the modern java.time classes defined in JSR 310 and built into Java 8 and later.
As of JDBC 4.2 we can exchange java.time objects with a database. Use getObject and setObject and updateObject.
The JDBC spec oddly requires support for OffsetDateTime but not the more commonly used Instant and ZonedDateTime. Your particular driver may support these other types. If not, convert.
Retrieval from database.
OffsetDateTime odt = myResultSet.getObject( … , OffsetDateTime.class ) ;
Instant instant = odt.toInstant() ;
Sending to the database.
OffsetDateTime odt = instant.atOffset( ZoneOffset.UTC ) ;
myPreparedStatement.setObject( … , odt ) ;
I need to pass in a Date object into a service which my API is calling. I have the info on the day, month, and year for the Date but also need a timestamp. The service is expecting it in this format:
<date>2015-04-01T00:00:00-05:00</date>
How can I add something to the Date to get this format?
Never use java.util.Date. Supplanted by java.time.Instant.
Get your date portion.
LocalDate ld = LocalDate.of( 2015 , 4 , 1 ) ;
Or use the readable Month enum.
LocalDate ld = LocalDate.of( 2015 , Month.APRIL , 1 ) ;
Get the time of day when the day starts in some particular time zone. Do not assume the day starts at 00:00:00, may be some other time such as 01:00:00. Let java.time figure that out for you.
ZoneId z = ZoneId.of( "America/Montreal" ) ;
ZonedDateTime zdt = ld.atStartOfDay( z ) ;
Generate a string in your desired format, a standard ISO 8601 format.
DateTimeFormatter f = DateTimeFormatter.ISO_OFFSET_DATE_TIME ;
String output = zdt.format( f ) ;
To see that moment in UTC, extract a Instant.
Instant instant = zdt.toInstant() ;
Conversion
If you must inter-operate with old code not yet updated for java.time, you can call new conversion methods added to the old classes. These include Date::from( Instant ).
java.util.Date d = java.util.Date.from( instant ) ;
Going the other direction.
Instant instant = d.toInstant() ;
Get back to a time zone other than UTC.
ZonedDateTime zdt = instant.atZone( ZoneId.of( "Pacific/Auckland" ) ) ; // Same moment, different wall-clock time.
Working with dates in Java is an ugly mess, always has been. Date class is mostly deprecated now. I am using LocalDateTime where you can construct it by calling year, month, day, hour, minute, and seconds. Here is what I could come up with:
LocalDateTime ldt = LocalDateTime.of(1997, Month.SEPTEMBER, 2, 1, 23, 0);
ZonedDateTime systemTime = ldt.atZone(ZoneId.systemDefault());
DateTimeFormatter formatter = DateTimeFormatter.ISO_OFFSET_DATE_TIME; //Basil's idea
System.out.println(systemTime.format(formatter));
Output:
1997-09-02T01:23:00-05:00
You could use SimpleDateFormat for this.
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
dateFormat.format(new Date());
I have this code to add 1 hour or 1 day in date Java 8, but doesn´t work
String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";
java.text.SimpleDateFormat format = new java.text.SimpleDateFormat(DATE_FORMAT);
Date parse = format.parse("2017-01-01 13:00:00");
LocalDateTime ldt = LocalDateTime.ofInstant(parse.toInstant(), ZoneId.systemDefault());
ldt.plusHours(1);
ZonedDateTime zdt = ldt.atZone(ZoneId.systemDefault());
Date te = Date.from(zdt.toInstant());
What´s wrong? The code shows: Sun Jan 01 13:00:00 BRST 2017
LocalDateTime is immutable and returns a new LocalDateTime when you call methods on it.
So you must call
ldt = ldt.plusHours(1);
Apart from the issue that you don't use the result of your date manipulation (ldt = ldt.plusHours(1)), you don't really need to go via a LocalDateTime for this operation.
I would simply use an OffsetDateTime since you don't care about time zones:
OffsetDateTime odt = parse.toInstant().atOffset(ZoneOffset.UTC);
odt = odt.plusDays(1).plusHours(1);
Date te = Date.from(odt.toInstant());
You could even stick to using Instants:
Instant input = parse.toInstant();
Date te = Date.from(input.plus(1, DAYS).plus(1, HOURS));
(with an import static java.time.temporal.ChronoUnit.*;)
tl;dr
LocalDateTime.parse( // Parse input string that lacks any indication of offset-from-UTC or time zone.
"2017-01-01 13:00:00".replace( " " , "T" ) // Convert to ISO 8601 standard format.
).atZone( // Assign a time zone to render a meaningful ZonedDateTime object, an actual point on the timeline.
ZoneId.systemDefault() // The Question uses default time zone. Beware that default can change at any moment during runtime. Better to specify an expected/desired time zone generally.
).plus(
Duration.ofDays( 1L ).plusHours( 1L ) // Add a span of time.
)
Details
Do not mix the troublesome old legacy classes Date and Calendar with the modern java.time classes. Use only java.time, avoiding the legacy classes.
The java.time classes use the ISO 8601 standard formats by default when parsing and generating strings. Convert your input string by replacing the SPACE in the middle with a T.
String input = "2017-01-01 13:00:00".replace( " " , "T" ) ;
LocalDateTime ldt = LocalDateTime.parse( input ) ;
ALocalDateTime does not represent an actual moment, not a point on the timeline. It has no real meaning until you assign a time zone.
ZoneId z = ZoneId.systemDefault() ; // I recommend specifying the desired/expected zone rather than relying on current default.
ZonedDateTime zdt = ldt.atZone( z ) ;
A Duration represents a span of time not attached to the timeline.
Duration d = Duration.ofDays( 1L ).plusHours( 1L ) ;
ZonedDateTime zdtLater = zdt.plus( d ) ;