2

So as in the title, in Java, if I do

System.out.print(new java.util.Date(Long.MAX_VALUE));
System.out.print(new java.util.Date(0));
System.out.print(new java.util.Date(-86400000));
System.out.print(new java.util.Date(Long.MIN_VALUE));

I get as output

Sun Aug 17 02:12:55 EST 292278994
Wed Dec 31 19:00:00 EST 1969
Tue Dec 30 19:00:00 EST 1969
Sun Dec 02 11:47:04 EST 292269055

So year values go Long.MAX_VALUE > Long.MIN_VALUE > 0 > -86400000? What happened? Why isn't Date(Long.MIN_VALUE) a negative year?

Searching, I wasn't able to find any information related to this, and the Java documentation doesn't mention anything about it.

I am using Java 8.

Also, executing the following

(new java.util.Date(Long.MIN_VALUE).getYear() >= new java.util.Date(0).getYear())

returns true, but

(new java.util.Date(Long.MIN_VALUE).after(new java.util.Date(0)))

returns false?

romellem
  • 5,792
  • 1
  • 32
  • 64
Tezra
  • 8,463
  • 3
  • 31
  • 68
  • 2
    Changing the formatter, you would have noticed that with `Long.MIN_VALUE` argument, the date is before christ! :-) – Rouliboy Apr 18 '17 at 13:10
  • 1
    As a courtesy, please leave comments explaining what is wrong with the question before down-voting so that it can be correct/avoided in the future. Thank you. – Tezra Apr 18 '17 at 13:40
  • @T.J.Crowder By 'proper formatter' you mean like GregorianCalendar? (Which gives me the same year) – Tezra Apr 18 '17 at 14:48
  • @T.J.Crowder Ok. Using SimpleDateFormater("y") gives me the year as a positive number, and SimpleDateFormater("Y") gives me the year as a negative number... because reasons I guess? X3 – Tezra Apr 18 '17 at 15:02
  • @Tezra: Man that's ugly. I was using the wrong format specifier anyway, it should have been `y`, which (as you say) prints a positive number. Well, at least that's consistent with GregorianCalendar. :-) I don't think `GregorianCalendar` or `Date` documents a range limit (in fact, `GregorianCalendar` says it can apply the rules infinitely forward or backward, but that doing so into the past is historically inaccurate), but I think you're blowing a range limitation somewhere. – T.J. Crowder Apr 18 '17 at 15:16
  • I still find it funny that seems to `Long.MAX_VALUE` correspond to 292277024 years after the epoch, but `Long.MIN_VALUE` to 292267086 years before the epoch. – Ole V.V. Apr 19 '17 at 10:44
  • Those of us who are happy to have transitioned to using [the newer `java.time` classes](http://docs.oracle.com/javase/8/docs/api/java/time/package-frame.html) may use this opportunity for noting that they come with well documented upper and lower bounds (year is +/– 999 999 999). – Ole V.V. Apr 19 '17 at 10:54
  • For `getYear()`, it’s deprecated, so you should not ne concerned about what it returns. :-) However, it returns an `int`, and the years you get overflow the `int` range, so which year is greater is somewhat random. – Ole V.V. Apr 19 '17 at 11:55
  • 1
    @OleV.V. It's not overflow, negative years just print as positive numbers, and toString() is not deprecated but it uses getYear so I am concerned. Also, for min-max years, you have to count leap years, and leap seconds, and there is no year 0 and skipped leap years, and however many other exceptions there are to the 'normal' flow of time X3 – Tezra Apr 19 '17 at 12:22
  • I’m taking back the part about overflow. 292278994 fits nicely in an `int`. It seems that `getYear()` subtracts 1900 from the year-of-era and returns the result. So `new java.util.Date(0).getYear()` gives 70 (possibly 69 in some time zones). The same happens BC: a date in year 2000 BC, `getYear()` will return 100. For `Long.MIN_VALUE` I would expect 292267155, which is clearly more than 70. – Ole V.V. Apr 19 '17 at 12:39

4 Answers4

4

First things first: The following

 Long.MIN_VALUE > -86400000

evaluates to false.

On the other hand, negative values for the constructor in the Date class mean dates in the calendar before "the epoch", namely January 1, 1970, 00:00:00 GMT.

So a date constructed with Long.MIN_VALUE is a way back in the past. Changing the way you print the Date may help you to spot the issue…

    System.out.println(new SimpleDateFormat("dd.MM.yyyy GG HH:mm:ss.SSS").format(new Date(Long.MAX_VALUE / 1000)));
    System.out.println(new SimpleDateFormat("dd.MM.yyyy GG HH:mm:ss.SSS").format(new Date(0)));
    System.out.println(new SimpleDateFormat("dd.MM.yyyy GG HH:mm:ss.SSS").format(new Date(-86400000)));
    System.out.println(new SimpleDateFormat("dd.MM.yyyy GG HH:mm:ss.SSS").format(new Date(Long.MIN_VALUE)));
    System.out.println(Long.MIN_VALUE > -86400000);

So, new Date(Long.MIN_VALUE) is a date before Christ.

The formatter patterns are very helpful to identify such spots.

In this case G means era designator, and all the pattern designators are here.

Sae1962
  • 1,122
  • 15
  • 31
ΦXocę 웃 Пepeúpa ツ
  • 47,427
  • 17
  • 69
  • 97
  • `yyyy` still shows the date for `Long.MIN_VALUE` in the future. I think the OP is just blowing a range limitation that isn't documented. – T.J. Crowder Apr 18 '17 at 15:17
  • You explanation would benefit greatly from (1) reminding us what the `GG` letters are in a date format sonce these are not used often (2) including the output of your code so we don’t have to run it ourselves to see the point. – Ole V.V. Apr 19 '17 at 10:39
  • Hi @OleV.V., i will update the aswern, thansk for the sugestion – ΦXocę 웃 Пepeúpa ツ Apr 19 '17 at 10:42
3

OK, so digging through Date source, getYear() is

/**
* Returns the difference between the year represented by this
* <code>Date</code> object and 1900.
*
* @return the year minus 1900 represented by this date object.
* @deprecated Use Calendar instead of Date, and use get(Calendar.YEAR)
* instead.  Note the 1900 difference in the year.
* @see Calendar
* @see #setYear(int)
*/
public int getYear() {
  Calendar cal = Calendar.getInstance();
  cal.setTimeInMillis(time);
  return cal.get(Calendar.YEAR) - 1900;
}

So, Date.getYear() is actually Calendar.getYear() - 1900. (Why -1900?)

Subtract 1900 why? Between 1960 and 1995 years were usually written with just two digits, e.g., 60 or 95. When Java came out, the ”year 2000 problem“ arising from this fact was being talked about, so they didn’t want to return just year % 100, the last two digits. So they figured subtracting 1900 would still allow you to figure out the century, and everyone should be happy. Well, we aren`t really. :-) – Ole V.V.

Calendar.getInstance() (source) defaults to java.util.GregorianCalendar (source).

So, in the Calendar abstract…

public void setTimeInMillis(long time) {
  clear();
  this.time = time;
  isTimeSet = true;
  computeFields();
}

And computeFields() is long and complicated, but tracing it long enough. you find

if (year <= 0) {
  fields[ERA] = BC;
  fields[YEAR] = 1 - year;
} else {
  fields[ERA] = AD;
  fields[YEAR] = year;
}

SO! Long story short, Date uses the GregorianCalendar, which displays negative years as a positive number, but Date doesn't have a way to check the Calendar era… So don't use Date for dates before the year 1 AD. And this is where encapsulation fails; you expect 1 behaviour, but inherit another that you are never told about! (I feel stupid now, but at least I wasn't the only one confused about this behaviour.)

Sae1962
  • 1,122
  • 15
  • 31
Tezra
  • 8,463
  • 3
  • 31
  • 68
  • Subtract 1900 why? Between 1960 and 1995 years were usually written with just two digits, e.g., 60 or 95. When Java came out, the ”year 2000 problem“ arising from this fact was being talked about, so they didn’t want to return just `year % 100`, the last two digits. So they figured subtracting 1900 would still allow you to figure out the century, and everyone should be happy. Well, we aren`t really. :-) – Ole V.V. Apr 20 '17 at 05:47
2

The other Answers are correct. A few more notes here.

java.time

Why is new java.util.Date(Long.MIN_VALUE) in Java

New? Not new at all. The java.util.Date class is very old, bundled with the original release of Java.

That troublesome old class is now legacy, supplanted by the java.time classes. Specifically, the Instant class. To convert to/from, see new methods added to the old class.

As for the maximum/epoch/minimum values, those are defined as constants.

Beware of using the limits

Beware of making any practical use of the minimum or maximum value.

If your intent is for something like meaning "unknown value", use another value. Many other tools such as databases will have very different limits, so you may run into problems. Also, such extreme values may not have any recognizable meaning to future readers/programmers/admins. Such values may even be mistaken for an error or bug.

If working with only recent dates, I would use the Instant.EPOCH for such a flag. The start of 1970 is recognizable by many technical folks as being special.

ISO 8601

The strings seen above are all in standard ISO 8601 format.

The java.time classes use these standard formats by default when parsing/generating strings.

To generate strings in other formats, see the DateTimeFormatter class.

Java 8 and later

I am using Java 8.

Then you should definitely be migrating from the old classes to modern java.time classes. They are an enormous improvement.


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.

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.

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
  • Very useful information. Just to clarify, I meant 'new' as in the java keyword. Also, the original intent of using a date.min was actually for a start field meant to indicate "since the dawn of time". I have since settled on "Since 1AD" so that I don't have to worry about what era, and that should function the same the dawn of time for my system. – Tezra Feb 12 '18 at 14:54
1

I found a possible implementation of java.util.Date where the final two lines of toString() are

1029    sb.append(' ').append(date.getYear());  // yyyy
1030    return sb.toString();

So toString relies on getYear, which in turn gives the observed behaviour, e.g.

Date d = new Date(Long.MIN_VALUE);
System.out.println(d.getYear());

will indeed print

292267155

Olaf Dietsche
  • 72,253
  • 8
  • 102
  • 198
  • OK, but can you explain why there is any output at all, rather than an exception or something? – Tim Biegeleisen Apr 18 '17 at 13:15
  • I'm using Java 8 which says "Allocates a Date object and initializes it to represent the specified number of milliseconds since the standard base time known as "the epoch", namely January 1, 1970, 00:00:00 GMT." The old message would explain why Long.Min is wrong, but not why the output is a date between 0 and max. – Tezra Apr 18 '17 at 13:19
  • @RobinTopper For the > chain, I'm going off the printed year... but it seems internally there is a discrepancy in printed date and date value? – Tezra Apr 18 '17 at 13:25
  • @OlafDietsche Why is a negative value not allowed? That would mean that Date must not be used for any dates before 1 January 1970. – VivekRatanSinha Apr 18 '17 at 13:30