Good way to convert integer YYYYMMDD into java.util.Date with local time zone

老子叫甜甜 提交于 2020-01-30 13:09:25

问题


I understand this question could look like FAQ subject but critical things here is time zone and performance. I have integer YYYYMMDD date (20150131 is example). Here is good 'almost working' solution:

import org.joda.time.DateTime;
import java.util.Date;

// ...

public Date extract(final int intDate) {
    Date result = null;

    try {
        result = new DateTime(
                intDate / 10000,
                (intDate / 100) % 100,
                intDate % 100,
                0,
                0,
                0,
                0).toDate();

    } catch (final IllegalArgumentException e) {
        // Log failure
    }
    return result;
}

'almost' is because having 0x0126810d and EET (UTC+2 / +3 when DST) time zone I receive:

java.lang.IllegalArgumentException: Illegal instant due to time zone offset transition: 1930-06-20T22:00:00.000

At least using JODA 1.6. I cannot switch easily. But I'd like it to be 1930-06-21T00:00:00.000+02:00 and I don't care about UTC representation.

  1. Is it possible at all (can java.util.date store such date)?
  2. OK, any better high performance way to achieve this (JODA is just remedy here, not critical)?

Yes, I understand this time does not exist:

roman@node4:$ zdump -v Europe/Kiev | grep 1930
Europe/Kiev  Fri Jun 20 21:59:59 1930 UTC = Fri Jun 20 23:59:59 1930 EET isdst=0 gmtoff=7200
Europe/Kiev  Fri Jun 20 22:00:00 1930 UTC = Sat Jun 21 01:00:00 1930 MSK isdst=0 gmtoff=10800

回答1:


java.time and LocalDate

No matter if using Joda-Time (as in your question) or java.time, the modern Java date and time API, I believe that the solution to your problem is using LocalDate. I suggest that you simply stick to this and neither use org.joda.time.DateTime nor java.util.Date. In particular not the latter, it was always poorly designed and is now long outdated.

I am presenting to ways.

    int intDate = 19300621; // 0x0126810d

    String asString = String.valueOf(intDate);
    LocalDate date = LocalDate.parse(asString, DateTimeFormatter.BASIC_ISO_DATE);

    System.out.println(date);

1930-06-21

I find this code clearer to read than the code doing divisions and modulo operations. It’s not as efficient, but for more than 19 out of 20 cases this should be no concern. If you like the divisions, you can of course do them with java.time too:

    int year = intDate / 10000;
    int monthDay = intDate % 10000;
    int month = monthDay / 100;
    int day = monthDay % 100;
    LocalDate date = LocalDate.of(year, month, day);

If you do need a java.util.Date for a legacy API not yet upgraded to java. time (or Joda-Time), convert like this (no matter which of the above conversions you used):

    Instant asInstant = date.atStartOfDay(ZoneId.systemDefault()).toInstant();
    Date oldfashionedDate = Date.from(asInstant);
    System.out.println(oldfashionedDate);

Output when my default time zone is set to Europe/Zaporozhye:

Sat Jun 21 01:00:00 EET 1930

(We notice that we get 01:00:00 because of transition to summer time (DST). The time of 00:00:00 didn’t exist on this day in this time zone.)

If still using Joda-Time

If you are still using Joda-Time, your own answer using toDateTimeAtStartOfDay() is just fine.

PS I reproduced your problem with Joda-Time 2.9.9, my time zone set to Europe/Zaporozhye and your integer of 19300621 (don’t know why you gave it as hex, 0x0126810d). I got an exception similar to yours: org.joda.time.IllegalInstantException: Illegal instant due to time zone offset transition (daylight savings time 'gap'): 1930-06-21T00:00:00.000 (Europe/Zaporozhye).

Link

Oracle tutorial: Date Time explaining how to use java.time.




回答2:


I suggest the following:

Calendar cal = Calendar.getInstance();
cal.set(Calendar.YEAR, year);
cal.set(Calendar.MONTH, month);
cal.set(Calendar.DAY_OF_MONTH, dayOfMonth);
result = cal.getTime();



回答3:


OK, I think I managed to replicate your problem, by doing:

    TimeZone.setDefault(TimeZone.getTimeZone("Europe/Kiev"));
    System.out.println( extract(0x0126810d));

(Previously I tried that with "EET", but apparently that gets a different time zone altogether)

I get an illegal argument exception, though the date it mentions is a bit different. This could be because of my version of Joda.

Illegal instant due to time zone offset transition (daylight savings time 'gap'): 1930-06-21T00:00:00.000 (Europe/Kiev)

Well, the way to solve it is not to be in the Europe/Kiev zone, at least for the sake of the Joda conversion:

public static Date extract(final int intDate) {
    Date result = null;
    DateTimeZone tz = DateTimeZone.forOffsetHours(2);

    try {
        result = new DateTime(
                intDate / 10000,
                (intDate / 100) % 100,
                intDate % 100,
                0,
                0,
                0,
                0,
                tz).toDate();

    } catch (final IllegalArgumentException e) {
        System.err.println(e.getMessage());
        return null;
    }
    return result;
}

This would avoid the error. You could move the tz variable definition and initialization to a field if you wish to improve performance in case you are calling the extract method a lot of times.

Mind you that when you print the resulting Date object using the default date format, which in turn uses the default time zone (Europe/Kiev), the result would be:

Sat Jun 21 01:00:00 EET 1930

You can print it properly with:

SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");
fmt.setTimeZone(TimeZone.getTimeZone("GMT+2"));


System.out.println(fmt.format( extract(0x0126810d)));

But maybe if you don't want to take DST into consideration, you should just work with the dates as if they were UTC. It depends what you want to do with them, really.

One last note: it's really easy to achieve the same result with Calendar:

public static Date extract2(final int intDate) {
    cal.set(intDate / 10000, ( intDate / 100 ) % 100 - 1, intDate % 100);
    return cal.getTime();
}

Where cal is a Calendar instance set in a field to avoid repeatedly creating and clearing it:

public static final Calendar cal;
static {
    cal = Calendar.getInstance();
    cal.clear();
}

(Mind multithreading, though).

Not sure about the performance problems you mentioned, and how critical the difference is.




回答4:


OK, finally got the following fragment and it works most close to my expectations. Like SDF but many times faster - like no string parsing just to get digits:

import org.joda.time.DateTime;
import org.joda.time.LocalDate;

public static Date toDateJoda(int intDate) {
    LocalDate ldt = new LocalDate(
        intDate / 10000,
        (intDate / 100) % 100,
        intDate % 100);

    DateTime dt = ldt.toDateTimeAtStartOfDay();
    return dt.toDate();
}

Parses everything and gets next valid date / time for cases like mine.



来源:https://stackoverflow.com/questions/27910196/good-way-to-convert-integer-yyyymmdd-into-java-util-date-with-local-time-zone

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!