Reputation: 589
I have a java.util.Date on which i am using org.apache.commons.lang.time#truncate(javaUtilDateObj, Calendar.DATE) to convert the date to midnight.
The problem is the UTC date gets represented in local timezone and the truncate method just converts the Hour,minute and seconds to 00:00:00 without considering the timezone.
Example :- Say UTC time is 30 seconds from epoch. Wed Dec 31 19:00:30 EST 1969 is the Date object representation I am seeing. On calling DateUtils#truncate() method on the above date, the output is Wed Dec 31 00:00:00 EST 1969.
What i am expecting is if UTC time is 30 seconds from epoch. And if that can be represented/converted to Thu Jan 1 00:00:30 EST 1970, i can call DateUtils#truncate() method on that and expect Thu Jan 1 00:00:00 EST 1970.
Note: I am not in a position to use joda-time API and hence I am stuck with what i have.
Upvotes: 0
Views: 3280
Reputation: 5554
Following function is usefull if your data layer saves UTC times but your presentation layer has different timeZone and you need to truncate a Date for filtering.
/**
* Truncates the given UTC date for the given TimeZone
*
* @param date UTC date
* @param timeZone Target timezone
* @param field Calendar field
*
* @return
*/
public static Date truncate(Date date, final TimeZone timeZone, final int field)
{
int timeZoneOffset = timeZone.getOffset(date.getTime());
// convert UTC date to target timeZone
date = DateUtils.addToDate(date, Calendar.MILLISECOND, timeZoneOffset);
// truncate in target TimeZone
date = org.apache.commons.lang3.time.DateUtils.truncate(date, field);
// convert back to UTC
date = DateUtils.addToDate(date, Calendar.MILLISECOND, -timeZoneOffset);
return date;
}
/**
* Adds the given amount to the given Calendar field to the given date.
*
* @see Calendar
*
* @param date the date
* @param field the calendar field to add to
* @param amount the amount to add, may be negative
*
* @return
*/
public static Date addToDate(final Date date, final int field, final int amount)
{
Objects.requireNonNull(date, "date must not be null");
final Calendar c = Calendar.getInstance();
c.setTime(date);
c.add(field, amount);
return c.getTime();
}
Upvotes: 0
Reputation: 340070
First moment of the day:
ZoneId z = ZoneId.of( "Pacific/Auckland" ) ; // Specify the region whose wall-clock time you want to perceive “today”. Specify time zone with `Continent/Region`, never the 3-4 letter pseudo-zones such as `EST` or `IST`.
LocalDate today = LocalDate.now( z ) ; // Capture the current date as seen in a specific time zone.
ZonedDateTime zdt = today.atStartOfDay( z ) ; // Let java.time determine first moment of the day.
Instant instant = zdt.toInstant() ; // Adjust from time zone to UTC. Same moment, same point on the timeline, different wall-clock time.
Many problems at play here.
if UTC time is 30 seconds from epoch. And if that can be represented/converted to Thu Jan 1 00:00:30 EST 1970,
No, wrong. You cannot convert. Those two date-time values do not represent the same moment. If by EST
you meant a time zone such as America/New_York
or America/Montreal
, then those two values are several hours apart.
Instant thirtySecondsFromEpoch = Instant.EPOCH.plusSeconds( 30 ) ;
thirtySecondsFromEpoch.toString(): 1970-01-01T00:00:30Z
See that same moment in much of the east coast of North America.
ZoneId z = ZoneId.of( "America/New_York" ) ;
ZonedDateTime zdtAdjustedFromThirtySecondsFromEpoch = thirtySecondsFromEpoch.atZone( z ) ;
zdtAdjustedFromThirtySecondsFromEpoch.toString(): 1969-12-31T19:00:30-05:00[America/New_York]
Because most of the people on the east coast of North America use a wall-clock time that is five hours behind UTC, that same moment of 30 seconds past epoch is seen as half a minute past 7 PM the day before, the last day of 1969.
Try the same time-of-day, 30 seconds into the first day of 1970, but as seen in America/New_York
time zone rather than in UTC. Now we are talking about a different moment entirely. Half a minute into a new day happens five hours later than a half a minute into new day in UTC.
ZonedDateTime zdt = ZonedDateTime.of( 1970 , 1 , 1 , 0 , 0 , 30 , 0 , z ) ;
zdt.toString(): 1970-01-01T00:00:30-05:00[America/New_York]
zdt.toInstant().toString(): 1970-01-01T05:00:30Z
You ignore the crucial issue of time zone.
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.
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(!).
java.util.Date::toString
The toString
method on Date
has a well-intentioned but troublesome behavior of implicitly applying the JVM’s current default time zone while generating a string representing the Date
object’s UTC value. Creates the false impression that this zone is present in the Date
.
This is one of many reasons to avoid this awful class. Use java.time classes instead.
The modern approach uses the java.time classes that supplant the troublesome old legacy date-time classes such as Date
.
If you have a Date
in hand, convert to/from java.time.Instant
via new methods added to the old class.
Instant instant = myJavaUtilDate.toInstant() ;
Avoid talking about “midnight” as that is an amorphous topic. Instead, focus on the first moment of the day, the start of the day.
Do not assume a day starts at 00:00:00. Anomalies such as Daylight Saving Time (DST) mean the day may start at another time-of-day such as 01:00:00. Let java.time determine the start of the day.
Determining the start of the day means determining the date. And determining the date requires a time zone, as mentioned above. So we need to adjust from your Instant
in UTC to a ZonedDateTime
in a specific time zone (ZoneId
) .
ZoneId z = ZoneId.of( "Africa/Tunis" ) ;
ZonedDateTime zdt = instant.atZone( z ) ;
Extract from that ZonedDateTime
just the date-only portion.
LocalDate ld = zdt.toLocalDate() ;
Now ask for the first moment of the day in a specified time zone.
ZonedDateTime zdtStartOfDay = ld.atStartOfDay( z ) ;
You can truncate various java.time objects. Look for a truncatedTo
method where you pass a TemporalUnit
object (likely a ChronoUnit
) to specify the granularity you want in your result. No need for the Apache DateUtils for this purpose.
If truncating Instant
you are always using UTC for the time zone logic.
Instant instantTrucToDay = Instant.now().truncatedTo( ChronoUnit.DAYS ) ;
More likely, you will want to truncate within the context a some time zone other than UTC as discussed above.
ZonedDateTime zdtTruncToDay = zdt.truncatedTo( ChronoUnit.DAYS ) ;
By the way, if your ultimate goal is to work with an entire date, a date-only without any time-of-day or zone, just stick to using LocalDate
.
if UTC time is 30 seconds from epoch. And if that can be represented/converted to Thu Jan 1 00:00:30 EST 1970
Not sure what you meant here. But keep in mind that given 30 seconds after the epoch of 1970-01-01T00:00:00Z (1970-01-01T00:00:30Z), the same moment in the east coast of North America is several hours earlier. That means around 7 PM the previous day.
Instant instantThirtySecsAfterEpoch = Instant.EPOCH.plusSeconds( 30 ) ;
instantThirtySecsAfterEpoch.toString(): 1970-01-01T00:00:30Z
ZoneId z = ZoneId.of( "America/New_York" ) ;
ZonedDateTime zdt = instantThirtySecsAfterEpoch.atZone( z ) ;
zdt.toString(): 1969-12-31T19:00:30-05:00[America/New_York]
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.
With a JDBC driver complying with JDBC 4.2 or later, you may exchange java.time objects directly with your database. No need for strings or java.sql.* classes.
Where to obtain the java.time classes?
The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval
, YearWeek
, YearQuarter
, and more.
Upvotes: 0
Reputation: 2965
Calendar utc = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
utc.setTime(date);
Calendar midnight = DateUtils.truncate(utc, Calendar.HOUR_OF_DAY);
Date midnightDate = midnight.getTime();
Upvotes: 1
Reputation: 53849
Use the Calendar
version of truncate
:
Calendar utc = Calendar.getInstance(TimeZone.getTimeZone("UTC")).setTime(date);
Calendar midnight = DateUtils.truncate(utc, Calendar.HOUR_OF_DAY);
Date midnightDate = midnight.getTime();
Upvotes: 0