3

I'm looking for a Java 6 compatible way of generating Seconds since epoch in GMT / UTC (aka UNIX Timestamp) out of a java.sql.Date Object.

My Java 8 code is:

Long seconds = dateObject.toLocalDate().atStartOfDay(ZoneId.of("GMT")).toEpochSecond()

My Java 6 code is:

final Calendar cal = Calendar.getInstance();
cal.setTime(dateObject);
final int year = cal.get(Calendar.YEAR);
final int month = cal.get(Calendar.MONTH);
final int day = cal.get(Calendar.DAY_OF_MONTH);

final GregorianCalendar c = new GregorianCalendar();
c.clear();
c.setGregorianChange(new Date(Long.MIN_VALUE));
c.setTimeZone(TimeZone.getTimeZone("GMT"));
c.set(Calendar.YEAR, year);
c.set(Calendar.MONTH, month);
c.set(Calendar.DAY_OF_MONTH, day);

Long seconds = c.getTimeInMillis() / 1000

Unfortunally, the Java 6 code is around as half as fast as the Java 8 code. I had a Java 6 solution with less code in the past (and therefor faster) but it was wrong when it came to unusual dates as "0001-01-01" where setGregorianChange() is needed and a second GergorianCalendar came in.

Any idea what I could do to get a fast Java 6 implementation?

  1. I need Thread safety
  2. "2010-01-01" must result in 1262304000
  3. "0001-01-01" must result in -62135596800
  4. "2999-12-31" must result in 32503593600

EDIT 1

I also tried the following Java 6 code:

final TimeZone tz = TimeZone.getDefault();
Long seconds = (dateObject.getTime() + tz.getRawOffset()) / 1000;

This works with "2010-01-01" and "2999-12-31" but not with "0001-01-01" (expected: -62135596800, actual: -62135769600) - guess the missing "setGregorianChange" is what troubles here.

EDIT 2

The code is running in a JsonSerializer. I have an application presenting those UNIX timestamps inside a JSON. The consuming side expects the timestamps as GMT Timestamps and will create a date by this timestamps hardcoding the timezone to GMT but use it later as it where local timezone.

To make the problem more clear, I'll present an example.

  • Date on my side: 2010-01-01 (UTC+100)
  • milliSeconds: 1262300400000
  • seconds: 1262300400

the consumer now takes this seconds and forms a date out of it:

  • Thu, 31 Dec 2009 23:00:00 GMT

Of course - this is 100% correct, but the consumer does use this date as if it where a local timezone date...

The seconds I expect to be transfered are 1262300400 + 3600 (my TZ Offset) for this example.

EDIT 3

I got the GregorianCalendar-thing down to a 4liner - but even this is much slower than the Java 8 thing. The problem is the creation of a new GregorianCalendar Object each time the code is executed. But this is a must-do since Calendar not threadsafe. So a fast solution must somehow work without any object-instantiation. The example below is mostly like EDIT 1 and is even faster than the Java 8 code. It just does not work correctly with dates before the Julian -> Gregorian switch (15-Oct-1582).

public class UnixTimestampSerializer extends JsonSerializer<Date> {

    @Override
    public void serialize(final Date dateObject, final JsonGenerator jgen, final SerializerProvider provider)
            throws IOException, JsonProcessingException {
        jgen.writeNumber((dateObject.getTime() - dateObject.getTimezoneOffset() * 60 * 1000) / 1000)
    }
}

Unfortunally it works a) with a deprecated method and b) does not work correctly with dates before 1582 :(. Using a static TimeZone.getDefault().getRawOffset() to solve a) is not an option, as the TimeZone may change (summer- vs. wintertime) on each date.

Olli
  • 689
  • 1
  • 6
  • 13
  • 2
    I'm gonna be "that guy" and ask if you have tried joda time http://www.joda.org/joda-time ? – Damien O'Reilly Sep 10 '15 at 17:02
  • 1
    Why are you getting a Calendar instance and the proceed to create *another* GregorianCalendar, completely ignoring the calendar obtained before. Start by removing the dead code? – Durandal Sep 10 '15 at 17:04
  • @DamienO'Reilly - I want a solution without any dependencies. – Olli Sep 10 '15 at 17:43
  • @Olli If you read the docs A java.sql.Date is a wrapper around the time stamp. #getTime() Returns the number of milliseconds since January 1, 1970, 00:00:00 GMT represented by this Date object. You might change he timezone for representation, but it is still the same timestamp. – matt Sep 10 '15 at 17:48
  • @Durandal - I could create a `GregorianCalendar`, call `setTime` do the y/m/d extraction as I do above, and then continue with `clear` and so on. This will indeed save me the first `Calendar.getInstance()` but does not change the execution time. _127,980 executions_ Original Java6 code:_35,402ms_, One-Calendar-Instance-Java6 code: _35,794ms_, Java8 code: **19,086ms** – Olli Sep 10 '15 at 17:56
  • @matt Thanks, but this is clear for me. The problem is, the object I have at hand contains the ms since epoch **respecting the local timezone**. I have this date and I now need the seconds representing the same date but in GMT. This is the root problem. One example. having a java.sql.Date representing 2010-01-01 just "out of nowhere" will have 1262300400000 in my timezone (UTC+1). If you just call getTime() and divide it by 1000, you get 1262300400. But the GMT representation of 2010-01-01 as UNIX timestamp is 1262304000 – Olli Sep 10 '15 at 18:00
  • @Olli You are saying that 2010-01-01 00:00:00(UTC+1) has a different timestamp than 2010-01-01 00:00:00(GMT). That is true. A Date object should wrap a time stamp though, and not a year/month/day etc. – matt Sep 10 '15 at 18:15
  • @matt exactly. To just strip it down: I need a Java 6 code which does exactly the same as the Java 8 code does without additional dependencies ;) It seems that I have a problem explaining without ambiguity what I need.... – Olli Sep 10 '15 at 18:53
  • I just noticed your benchmark numbers look *very* odd. 127k conversions taking 35 seconds? Thats seems very unplausible if it were only the conversion time. What exactly are you measuring? – Durandal Sep 10 '15 at 18:59
  • It is the execution done by Jackson on serializing JSON.It is just for comparison, please don't get distracted by that (JProfiler, slow machine, JMeter on the same machine and so on). – Olli Sep 10 '15 at 19:01

2 Answers2

3
java.sql.Date date = ...;
long unixTimestamp = date.getTime() / 1000;
Roman
  • 6,486
  • 2
  • 23
  • 41
  • the question was: *GMT* – Olli Sep 10 '15 at 17:41
  • 2
    The number of seconds between Epoch and some point in time (`date`) doesn't depend on timezones. – Roman Sep 10 '15 at 17:44
  • Epoch has a time zone and `date` has a time zone too... - I assume that the issue is that he has to ensure that they have the same (GMT). – Christian Fries Sep 10 '15 at 18:02
  • @Roman - a regulary created java.sql.Date object will represent the ms since epoch **in your local timezone** when you just call getTime() you'll exactly get that. – Olli Sep 10 '15 at 18:09
  • @Olli the javadocs exactly contradict what you are saying. The epoch is January 1, 1970 00:00:00.000 GMT. So if you have a date ''in a different timezone'' it has the same number of milliseconds since the epoch. Are you saying your date has been created from an incorrect epoch? – matt Sep 10 '15 at 18:19
  • Yes, this is what the javadoc says. But try it yourself. Just make new Date(System.currentTimeMillies()) and look inside the resulting object. getTime() does produce 1441909382970 here.Which is 1441909382 seconds since epoch which is _Thu, 10 Sep 2015 18:23:02 GMT_ but right now, it is 19:23 CEST here.... So If you have a Date which is set to 00:00:00 'o clock (of course it is java.sql.Date) and you'll fetch the seconds, you'll end up in my case a day earlier at 23:00:00. You see what I mean? Of course the Javadoc is 100% correct. The consumer of the seconds assumes it is GMT. – Olli Sep 10 '15 at 18:26
  • @Olli `java.sql.Date` (and `java.util.Date` for that matter) does not store years, months, days, etc. It stores a single integer (actually, `long`) - the number of milliseconds since January 1, 1970, 00:00:00 GMT ("the epoch"). So when you do `date.getTime()`, you get that same number of milliseconds since the epoch. The timezone info is used only when creating an instance of Date and only when using a deprecated constructor `Date(year, month, ...)` – Roman Sep 10 '15 at 18:29
  • @Olli It is now clear that what you really asking is how to take a `year`, `month`, and `day` values from a `java.sql.Date` and construct another Date so that it represents midnight, GMT, at the beginning of the day specified by the `year`, `month`, and `day` values. – Roman Sep 10 '15 at 18:36
  • @Olli I tried your example in two timezones, I get the same time stamp for both and if I just print the date out, it is correct for the timezone it is in. Which is consistent with the javadoc. Sorry that I don't see the issue. – matt Sep 10 '15 at 18:38
  • @matt maybe my EDIT 2 in the initial question makes things more clear of what I need.... – Olli Sep 10 '15 at 18:48
0

I don't know what would be faster than using currentTimeInMillis and converting to seconds. This will return in seconds from EPOCH, it should be pretty fast. Using a Date object seems unnecessary to me.

        BigDecimal seconds = BigDecimal.valueOf(System.currentTimeMillis() / 1000.0);
        System.out.println(seconds);
Jacob Briscoe
  • 252
  • 1
  • 8
  • I need GMT. This will result in seconds since epoch in the local timezone – Olli Sep 10 '15 at 17:41
  • But then, couldn't you just add the hours difference of the time zone? – Christian Fries Sep 10 '15 at 18:07
  • Yes - had the same idea some seconds ago. Good catch ;) But - it does not work for dates like 0001-01-01 as the default change from Julian to Gregorian Calendar gives troubles here. No idea when java assumes Julian - maybe before 1582? I don't know. But this causes problems in my case. I edited my initial posting for this. – Olli Sep 10 '15 at 18:17