Best way to get maximum Date value in java? - java

I'm writing a bit of logic that requires treating null dates as meaning forever in the future (the date in question is an expiration date, which may or may not exist). Instead of putting in special cases for a null date throughout the code, I want to just convert null into the maximum possible Date. I don't see any obvious ways to get such a value without hard coding it. What's the best way to get the maximum value of whatever Date implementation is being used?

Try
new Date(Long.MAX_VALUE)
which should give you the longest possible date value in Java.

Encapsulate the functionality you want in your own class, using Long.MAX_VALUE will most likely cause you problems.
class ExpirationDate {
Date expires;
boolean hasExpiration() {
return expires == null;
}
Date getExpirationDate() {
return expires;
}
boolean hasExpired(Date date) {
if (expires == null) {
return true;
} else {
return date.before(expires);
}
}
...
}

+1 to the Long.MAX_VALUE suggestions. It seems that this would help you if you sort stuff by your date field.
However, instead of constructing a date from some the large constant value where ever you need the date, use a globally visible singleton to hold a Date instance that represents your special value:
class DateUtil
{
public static final Date NO_EXPIRE = new Date( Long.MAX_VALUE );
}
Then you can use simple identity comparison (mydate == DateUtils.NO_EXPIRE) to test if a particular date is of your special case instead of obj.equals(); (ie. mydate.equals ( DateUtils.NO_EXPIRE ); )

Here is what I do:
public static final TimeZone UTC;
// 0001.01.01 12:00:00 AM +0000
public static final Date BEGINNING_OF_TIME;
// new Date(Long.MAX_VALUE) in UTC time zone
public static final Date END_OF_TIME;
static
{
UTC = TimeZone.getTimeZone("UTC");
final Calendar c = new GregorianCalendar(UTC);
c.set(1, 0, 1, 0, 0, 0);
c.set(Calendar.MILLISECOND, 0);
BEGINNING_OF_TIME = c.getTime();
c.setTime(new Date(Long.MAX_VALUE));
END_OF_TIME = c.getTime();
}
Note that if the TimeZone is NOT UTC you will get offsets from the "end of time", which won't be maximal values. These are especially useful for inserting into Database fields and not having to have NULL dates.

have you considered adopting the use of Joda Time?
It's slated to be included in java 7 as the basis for JSR-310
The feature that may interest you is ZeroIsMaxDateTimeField
which basically swaps zero fields for the maximum value for that field within the date-time.

From Java SE 8 you could use:
LocalDate.MAX

One problem I see is that for sorting on expiration date, using a null isn't easily sortable. So replacing with an actual value (even if it's an arbitrary sentry value well into the future) may be needed.
I suppose another way of treating "no expiration" is simply to say something expires 100 years in the future... Unless your database is dealing with long-term contracts!

I like Instant.MAX because it is more likely to be supported in the future than Long.MAX_VALUE.
Note that as of today, though, Instant.MAX.toEpochMilli() throws an overflow error.

Perhaps one option is to use the maximal system date. You can get it by using:
System.out.println(new Date(Long.MAX_VALUE).toString())
//Output:
//Sun Aug 17 12:42:55 IST 292278994

Related

Zone parsing for GMT0

I have GMT0 as the default timezone in a system and it causes problem when I'm serializing it and deserializing it just after that.
System.setProperty("user.timezone","GMT0");
DateTimeFormatter zoneFormatter = new DateTimeFormatterBuilder()
.appendZoneOrOffsetId()
.toFormatter();
String formatted = zoneFormatter.format(ZonedDateTime.now());
System.out.println(formatted);
System.out.println(zoneFormatter.parse(formatted));
The first System.out.println prints GMT0 while the second throws the following problem.
Exception in thread "main" java.time.format.DateTimeParseException: Text 'GMT0' could not be parsed, unparsed text found at index 3
at java.time.format.DateTimeFormatter.parseResolved0(DateTimeFormatter.java:1952)
at java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1777)
is it an expected behavior? Is there a way to do that in a safe manner?
As you noticed in the comments, that's a bug in JDK 8, fixed only in versions >= 9.
If you're using JDK 8 and can't/won't upgrade it, there's a workaround. You can treat the "GMT" part as a literal (the text "GMT" itself) and consider the 0 as the offset seconds, using the respective ChronoField:
DateTimeFormatter zoneParser = new DateTimeFormatterBuilder()
// text "GMT"
.appendLiteral("GMT")
// offset seconds
.appendValue(ChronoField.OFFSET_SECONDS)
.toFormatter();
System.out.println(zoneParser.parse("GMT0"));
Keep in mind that this works only for offset zero. For any other values (such as "GMT2" or "GMT-2") this won't work, because it'll consider the values "2" and "-2" as seconds, but they actually mean "hours".
In case you need to parse all offset values in this format ("GMTn")
Well, JDK 8 also can't handle one-digit offsets, and it always requires a signal, either + or -. So "GMT2" and "GMT-2" won't work with the current API.
There's a harder alternative, though: create your own TemporalField, representing "offset hours". All the details about how to do it are in the documentation, and I'm not sure if all methods are correctly implemented - I'm just sure about isSupportedBy, getFrom and adjustInto, the others maybe need some improvement/adjustment:
public class OffsetHours implements TemporalField {
#Override
public TemporalUnit getBaseUnit() {
return ChronoUnit.HOURS;
}
#Override
public TemporalUnit getRangeUnit() {
return ChronoUnit.FOREVER;
}
#Override
public ValueRange range() {
return ValueRange.of(ZoneOffset.MIN.getTotalSeconds() / 3600, ZoneOffset.MAX.getTotalSeconds() / 3600);
}
#Override
public boolean isDateBased() {
return false;
}
#Override
public boolean isTimeBased() {
return true;
}
#Override
public boolean isSupportedBy(TemporalAccessor temporal) {
return temporal.isSupported(ChronoField.OFFSET_SECONDS);
}
#Override
public ValueRange rangeRefinedBy(TemporalAccessor temporal) {
ValueRange rangeInSecs = temporal.range(ChronoField.OFFSET_SECONDS);
return ValueRange.of(rangeInSecs.getMinimum() / 3600, rangeInSecs.getMaximum() / 3600);
}
#Override
public long getFrom(TemporalAccessor temporal) {
return temporal.getLong(ChronoField.OFFSET_SECONDS) / 3600;
}
#Override
public <R extends Temporal> R adjustInto(R temporal, long newValue) {
return (R) temporal.with(ChronoField.OFFSET_SECONDS, newValue * 3600);
}
}
Now you create an instance of this field and use it in your parser:
// the new field
OffsetHours offsetHoursField = new OffsetHours();
DateTimeFormatter zoneParser = new DateTimeFormatterBuilder()
// text "GMT"
.appendLiteral("GMT")
// offset hours
.appendValue(offsetHoursField)
.toFormatter();
I also recommend creating a TemporalQuery to convert the parsed result to a ZoneOffset:
// get hours and create offset from hours value
TemporalQuery<ZoneOffset> getOffsetFromHours = temporal -> {
return ZoneOffset.ofHours((int) temporal.getLong(offsetHoursField));
};
Now you can parse it:
ZoneOffset offsetZero = zoneParser.parse("GMT0", getOffsetFromHours);
ZoneOffset offsetTwo = zoneParser.parse("GMT2", getOffsetFromHours);
ZoneOffset offsetMinusTwo = zoneParser.parse("GMT-2", getOffsetFromHours);
You can improve it letting the OffsetHours field to be a static instance (or maybe an enum), so you don't need to create it all the time.
Since Java 8, the ZoneId is responsible for handling timezone names and aliases according to IANA Time Zone Database (often referred as TZ DB, zdata). In particular, ZoneId.html#of is used for that conversion.
Different TZ DB versions are shipped by Oracle with different JREs: see tzdata versions and overview of how to update tzdata
The list of TZ DB identifiers is available on wikipedia (TZ column), as for release release 2017c, GMT0 is already listed as deprecated in there, and canonical name for this time zone is Etc/GMT
Despite there is no explicit instructions to use only canonical TZ names, it can be a best practice however, as deprecated aliases may be removed from further tzdata distributions (and thus from Zone.of support) without any special notice

jodatime DateTime millis cannot be set with actual epoch millis

I'm looking for a reasonable way to set the value of jodatime's DateTime millis. While debugging in Intellij IDEA (v15), I'm looking at the following code:
public String getDayOfWeek(String timezone) {
DateTime now = DateTime.now(DateTimeZone.forID(timezone));
return now.dayOfWeek().getAsText();
}
If I breakpoint the return statement for the purpose of mutating the value of now by changing the millis field of the DateTime instance to reflect a different (valid) epoch time with millis, Intellij errors that the value I'm attempting to set exceeds the capacity of int. It makes sense since epoch with millis is 13-digits and IIRC int can only store 2^32-1.
There isn't a visible field in the DateTime instance for epoch time without millis. I can successfully set the value with the 10-digit epoch time without millis, but clearly that's not going to evaluate properly; I only mention it to say that I'm able to set within-range values in the debugger successfully.
What options are there? Alternately, is there a more elegant way with jodatime to derive the current day of the week that also allows me to mutate the value? The caller doesn't assign the return value, rather, simply uses it for comparison. I don't want to scrap jodatime since this method is part of a quite large class of methods that all use it.
Thanks in advance for your time.
Maybe you ran into this issue because the value used was interpreted as an int instead of a long? You can use one of the DateTime constructors that takes a long parameter to specify the date and time in milliseconds. For example:
import org.joda.time.*;
import org.joda.time.format.*;
public class JodaTimeMillis {
public static void main(final String[] arguments) {
new JodaTimeMillis().run();
}
private void run() {
DateTimeZone timeZone = DateTimeZone.forID("Europe/Amsterdam");
String pattern = "MM/dd/yyyy HH:mm:ss.SSS";
DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern(pattern);
for (int milliseconds = 619; milliseconds < 629; milliseconds++) {
DateTime dateTime = new DateTime(1234567890000L + milliseconds, timeZone);
System.out.println(milliseconds + ": " + dateTimeFormatter.print(dateTime));
}
}
}
This gives the following output (on my laptop), which shows that the milliseconds part can be specified:
619: 02/14/2009 00:31:30.619
620: 02/14/2009 00:31:30.620
621: 02/14/2009 00:31:30.621
622: 02/14/2009 00:31:30.622
623: 02/14/2009 00:31:30.623
624: 02/14/2009 00:31:30.624
625: 02/14/2009 00:31:30.625
626: 02/14/2009 00:31:30.626
627: 02/14/2009 00:31:30.627
628: 02/14/2009 00:31:30.628
Do you want to run tests from the debugger? You could also consider using unit tests.

Comparing dates stored in date objects and getting a boolean returned

I am having trouble comparing dates in Java I have tried:
(today == actDate)
(today.equals(actDate))
Both always seem to evaluate to false:
In the image above the first date is today and the second is actDate.
Both are date objects:
Date today = new Date(System.currentTimeMillis());
Date actDate = new Date(taskHours.get(j).getDate().getTime());
Then I tried using compareTo, but this appears to return a 1 if the date is greater and a -1 if lower.
What am I doing wrong?
java.util.Date and java.sql.Date differ. This might be the reason.
your problem is Date today = new Date(System.currentTimeMillis());. With this you use the current system-time in milliseconds , which are stored in the date object. Your second date doesn't hold the information with milliseconds and this why every comparison fails.
Working with dates is a bit tricky. You need to use compareTo as JDBC works with java.sql.Timestamp and within application you usually work with java.util.Date. Two objects are (usually) not considered to be equal if they are instances of two different classes (even thou they might be from the same hierarchy).
What you want to do is:
public boolean isSameDate(Date date1, Date date2) {
return date1.compareTo(date2) == 0;
}
If you want to make your life easier when null values come in play, use commons-lang:
public boolean isSameDate(Date date1, Date date2) {
return ObjectUtils.compare(date1, date2) == 0;
}
If you want to compare just the date information (not the time), then I suggest you to use Joda Time library (namely LocalDate class). Converting timestamps to calendar date objects is not a straghtforward (one line of code) task with standard Java components.
You can use the Date#after(Date when) and Date#before(Date otherDate) methods to compare dates and achive some order.
You are comparing 2 different dates (maybe seconds are different), so equals method return false which is expected. Using equals is the right way to compere them.
Use a Calendar object instead. And manually compare day, month and year separately.
Calendar cal1 = new Calendar();
cal1.setTime(date1);
Calendar cal2 = new Calendar();
cal2.setTime(date2);
boolean equal = true;
equal &= cal1.get(Calendar.DAY_OF_MONTH) == cal2.get(Calendar.DAY_OF_MONTH);
equal &= cal1.get(Calendar.MONTH) == cal2.get(Calendar.MONTH);
equal &= cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR);

Comparing two dates using Joda time

I want to compare two dates, however I'm running into trouble. 1 date is created from a java.util.date object and the other is manually crafted. The following code is an example:
Date ds = new Date();
DateTime d = new DateTime(ds);
DateTime e = new DateTime(2012,12,07, 0, 0);
System.out.println(d.isEqual(e));
However the test turns out false. I am guessing that it is because of the time. How can I check if these two dates are equal to each other (I mean the Year, month, date are identical)?
System.out.println(d.toDateMidnight().isEqual(e.toDateMidnight()));
or
System.out.println(d.withTimeAtStartOfDay().isEqual(e.withTimeAtStartOfDay()));
You should use toLocalDate():
date1.toLocalDate().isEqual(date2.toLocalDate())
This will get rid of the Time part of the DateTime.
There is another approach, but it does not account for the case where the two dates have a different timezone, so it's less reliable:
date1.withTimeAtStartOfDay().isEqual(date2.withTimeAtStartOfDay())
return DateTimeComparator.getDateOnlyInstance().compare(first, second);
Via How to compare two Dates without the time portion?
If you want to ignore time components (i.e. you want to compare only dates) you can use DateMidnight class instead of Date Time. So your example will look something like this:
Date ds = new Date();
DateMidnight d = new DateMidnight(ds);
DateMidnight e = new DateMidnight(2012, 12, 7);
System.out.println(d.isEqual(e));
But beware, it will print "true" only today :)
Also note that by default JDK Date and all Joda-Time instant classes (DateTime and DateMidnight included) are constructed using default timezone. So if you create one date to compare in code, but retrieve another one from the DB which probably stores dates in UTC you may encounter inconsistencies assuming you are not in UTC time zone.
As they're DateTime objects, their time parts are also taken into consideration when you're comparing them. Try setting the time parts of the first date to 0, like:
d = d.withTime(0, 0, 0, 0);
I stumbled into this question while looking for a comparison with today. Here's how you can compare a date to today :
date1.toLocalDate().isBeforeNow() // works also with isAfterNow
This is a static method which works for me.
public static boolean isSameDay(DateTime date1, DateTime date2){
return date1.withTimeAtStartOfDay().isEqual(date2.withTimeAtStartOfDay());
}
DateTimeComparator.getDateOnlyInstance().compare(obj1, obj2);
obj1 and obj2 can be a String, Long, Date(java.util)... For the details see
http://www.joda.org/joda-time/apidocs/index.html?org/joda/time/DateTimeComparator.html
Write your own method
public boolean checkEqual(DateTime first,DateTime second){
if(first.<getterforyear> == second.<getterforyear> && first.<getterformonth> == second.<getterformonth> && first.<getterforday> == second.<getterforday>){
return true;
}
return false;
}

Best practice for ignoring year in date for date ranges

I need to model some information about seasons, and need to track them based on start / end date in a year-agnostic way. I.e. I need to allow users to define summer as being between, say, May 15th and September 10th, and do that for all years.
I'll need to do a lot of checking of the type isThisDateInSeason). All the date manipulation functions (i.e. Date, Calendar) seem to only work with valid dates, i.e. including year information.
Are there any best practices around how to do this? I can think of a bunch of hacky ways of doing it (i.e. store month and day of the month or to store dates and just bring them to a baseline year so I can can compare), but it seems like there might be a better way.
I am writing this in Java or Groovy.
Would the Joda-Time library help here? I don't have experience with it, but it looks to have much more flexibility.
I found this question about how to identify a season from date, but that focuses on months, and I need more flexibility to include dates.
If each user owns their own data (i.e. they specify their seasons and then they enter their own information) then you could just store the data with the season as part of it, however I have a feeling the scenario you are after is for shared data across numerous users who define seasons differently.
You would have to be very careful to 'normalize' dates as the leap year may cause unexpected problems, i.e. trying to set Feb 29 on a non leap year will cause problems / exceptions.
I have put the below together, unforutnatly its c# but the concept will be the same. I havnt actually tested the code, but as a even as psudo code it may help.
public class SeasonChecker
{
public enum Season {Summer, Autumn, Winter, Spring};
private List<SeasonRange> _seasons = new List<SeasonRange>();
public void DefineSeason(Season season, DateTime starting, DateTime ending)
{
starting = starting.Date;
ending = ending.Date;
if(ending.Month < starting.Month)
{
// split into 2
DateTime tmp_ending = new DateTime(ending.Year, 12, 31);
DateTime tmp_starting = new DateTime(starting.Year, 1, 1);
SeasonRange r1 = new SeasonRange() { Season = season, Starting= tmp_starting, Ending = ending };
SeasonRange r2 = new SeasonRange() { Season = season, Starting= starting, Ending = tmp_ending };
this._seasons.Add(r1);
this._seasons.Add(r2);
}
else
{
SeasonRange r1 = new SeasonRange() { Season = season, Starting= starting, Ending = ending };
this._seasons.Add(r1);
}
}
public Season GetSeason(DateTime check)
{
foreach(SeasonRange range in _seasons)
{
if(range.InRange(check))
return range.Season;
}
throw new ArgumentOutOfRangeException("Does not fall into any season");
}
private class SeasonRange
{
public DateTime Starting;
public DateTime Ending;
public Season Season;
public bool InRange(DateTime test)
{
if(test.Month == Starting.Month)
{
if(test.Day >= Starting.Day)
{
return true;
}
}
else if(test.Month == Ending.Month)
{
if(test.Day <= Ending.Day)
{
return true;
}
}
else if(test.Month > Starting.Month && test.Month < Ending.Month)
{
return true;
}
return false;
}
}
}
Note the above code makes the assumption that the season will not start and end on the same month - a fairly safe one I think!
I think you'll have to roll your own DateRange class, although this is such a common problem you would hope there is already a clever util out there that does it. As a general point when dealing with seasons you also have to take account of geography, which will make life really hard. In Oz we're the reverse of the US, so Summer runs from November to February.
Where is the data? If it is in a database I would consider making a table for dates (like a time dimension in OLAP). You can then calculate a column for your seasons as they are for financial quarters in in some Time Dimension examples. (this is assuming that your season does not change but has fixed dates).
All the "if date in season" type checking will be pre-built pushing the costs of calculating the season into your setup time rather then your run time.
Edit: Just saw your comment on user configurable season dates. This would still work as you can do a join in a database including the time dimension which could be easier working with the set of data then in Java.

Categories