将整数 YYYYMMDD 转换为具有本地时区的 java.util.Date 的好方法
Good way to convert integer YYYYMMDD into java.util.Date with local time zone
我知道这个问题可能看起来像常见问题解答主题,但这里的关键是时区和性能。我有整数 YYYYMMDD 日期(例如 20150131)。这是很好的 'almost working' 解决方案:
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' 是因为我收到的时区是 0x0126810d 和 EET(UTC+2 / +3 DST)时区:
java.lang.IllegalArgumentException: Illegal instant due to time zone
offset transition: 1930-06-20T22:00:00.000
至少使用 JODA 1.6。我不能轻易切换。
但我希望它是 1930-06-21T00:00:00.000+02:00
并且我不关心 UTC 表示。
- 是否可能(
java.util.date
可以存储这样的日期)?
- 好的,有没有更好的高性能方法来实现这个(JODA 只是补救,并不重要)?
是的,我明白这个时间不存在:
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
我建议如下:
Calendar cal = Calendar.getInstance();
cal.set(Calendar.YEAR, year);
cal.set(Calendar.MONTH, month);
cal.set(Calendar.DAY_OF_MONTH, dayOfMonth);
result = cal.getTime();
好的,我想我成功地重现了你的问题,方法是:
TimeZone.setDefault(TimeZone.getTimeZone("Europe/Kiev"));
System.out.println( extract(0x0126810d));
(之前我用 "EET" 尝试过,但显然完全不同的时区)
我得到一个非法参数异常,尽管它提到的日期有点不同。这可能是因为我的 Joda 版本。
Illegal instant due to time zone offset transition (daylight savings time 'gap'): 1930-06-21T00:00:00.000 (Europe/Kiev)
嗯,解决它的方法是不要在 Europe/Kiev 区域,至少为了 Joda 转换:
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;
}
这样可以避免错误。如果您希望在多次调用 extract
方法的情况下提高性能,您可以将 tz
变量定义和初始化移动到一个字段。
请注意,当您使用默认日期格式打印生成的 Date
对象时,该格式又使用默认时区 (Europe/Kiev),结果将是:
Sat Jun 21 01:00:00 EET 1930
您可以通过以下方式正确打印:
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)));
但是,如果您不想考虑夏令时,您应该像处理 UTC 一样处理日期。这取决于你想用它们做什么,真的。
最后一点:使用 Calendar
:
很容易达到相同的结果
public static Date extract2(final int intDate) {
cal.set(intDate / 10000, ( intDate / 100 ) % 100 - 1, intDate % 100);
return cal.getTime();
}
其中 cal
是在字段中设置的 Calendar
实例,以避免重复创建和清除它:
public static final Calendar cal;
static {
cal = Calendar.getInstance();
cal.clear();
}
(不过请注意多线程)。
不确定您提到的性能问题,以及差异的严重程度。
好的,终于得到了下面的片段,它的效果最接近我的预期。像 SDF 但快很多倍 - 就像没有字符串解析只是为了获取数字:
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();
}
解析所有内容并为像我这样的情况获取下一个有效日期/时间。
java.time 和 LocalDate
无论是使用 Joda-Time(如您的问题)还是 java.time,现代 Java 日期和时间 API,我相信您的问题的解决方案是使用 LocalDate
。我建议您只坚持这一点,不要使用 org.joda.time.DateTime
或 java.util.Date
。尤其是后者,它总是设计得很糟糕,现在已经过时了。
我正在介绍方法。
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
我发现这段代码比执行除法和模运算的代码更易读。它效率不高,但对于 20 个案例中的 19 个以上,这应该没有问题。如果你喜欢除法,你当然也可以用 java.time 来做:
int year = intDate / 10000;
int monthDay = intDate % 10000;
int month = monthDay / 100;
int day = monthDay % 100;
LocalDate date = LocalDate.of(year, month, day);
如果您确实需要 java.util.Date 用于尚未升级到 java 的旧版 API。时间(或 Joda-Time),像这样转换(无论您使用上述哪种转换):
Instant asInstant = date.atStartOfDay(ZoneId.systemDefault()).toInstant();
Date oldfashionedDate = Date.from(asInstant);
System.out.println(oldfashionedDate);
当我的默认时区设置为Europe/Zaporozhye时的输出:
Sat Jun 21 01:00:00 EET 1930
(我们注意到由于转换到夏令时 (DST),我们得到 01:00:00。00:00:00 的时间在这个时区的这一天不存在。)
如果还在使用 Joda-Time
如果您仍在使用 Joda-Time,您自己使用 toDateTimeAtStartOfDay()
的答案就可以了。
PS 我用 Joda-Time 2.9.9 重现了你的问题,我的时区设置为 Europe/Zaporozhye 和你的整数 19300621(不知道你为什么把它作为十六进制,0x0126810d ).我遇到了与您类似的异常: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 解释如何使用 java.time.
我知道这个问题可能看起来像常见问题解答主题,但这里的关键是时区和性能。我有整数 YYYYMMDD 日期(例如 20150131)。这是很好的 'almost working' 解决方案:
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' 是因为我收到的时区是 0x0126810d 和 EET(UTC+2 / +3 DST)时区:
java.lang.IllegalArgumentException: Illegal instant due to time zone offset transition: 1930-06-20T22:00:00.000
至少使用 JODA 1.6。我不能轻易切换。
但我希望它是 1930-06-21T00:00:00.000+02:00
并且我不关心 UTC 表示。
- 是否可能(
java.util.date
可以存储这样的日期)? - 好的,有没有更好的高性能方法来实现这个(JODA 只是补救,并不重要)?
是的,我明白这个时间不存在:
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
我建议如下:
Calendar cal = Calendar.getInstance();
cal.set(Calendar.YEAR, year);
cal.set(Calendar.MONTH, month);
cal.set(Calendar.DAY_OF_MONTH, dayOfMonth);
result = cal.getTime();
好的,我想我成功地重现了你的问题,方法是:
TimeZone.setDefault(TimeZone.getTimeZone("Europe/Kiev"));
System.out.println( extract(0x0126810d));
(之前我用 "EET" 尝试过,但显然完全不同的时区)
我得到一个非法参数异常,尽管它提到的日期有点不同。这可能是因为我的 Joda 版本。
Illegal instant due to time zone offset transition (daylight savings time 'gap'): 1930-06-21T00:00:00.000 (Europe/Kiev)
嗯,解决它的方法是不要在 Europe/Kiev 区域,至少为了 Joda 转换:
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;
}
这样可以避免错误。如果您希望在多次调用 extract
方法的情况下提高性能,您可以将 tz
变量定义和初始化移动到一个字段。
请注意,当您使用默认日期格式打印生成的 Date
对象时,该格式又使用默认时区 (Europe/Kiev),结果将是:
Sat Jun 21 01:00:00 EET 1930
您可以通过以下方式正确打印:
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)));
但是,如果您不想考虑夏令时,您应该像处理 UTC 一样处理日期。这取决于你想用它们做什么,真的。
最后一点:使用 Calendar
:
public static Date extract2(final int intDate) {
cal.set(intDate / 10000, ( intDate / 100 ) % 100 - 1, intDate % 100);
return cal.getTime();
}
其中 cal
是在字段中设置的 Calendar
实例,以避免重复创建和清除它:
public static final Calendar cal;
static {
cal = Calendar.getInstance();
cal.clear();
}
(不过请注意多线程)。
不确定您提到的性能问题,以及差异的严重程度。
好的,终于得到了下面的片段,它的效果最接近我的预期。像 SDF 但快很多倍 - 就像没有字符串解析只是为了获取数字:
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();
}
解析所有内容并为像我这样的情况获取下一个有效日期/时间。
java.time 和 LocalDate
无论是使用 Joda-Time(如您的问题)还是 java.time,现代 Java 日期和时间 API,我相信您的问题的解决方案是使用 LocalDate
。我建议您只坚持这一点,不要使用 org.joda.time.DateTime
或 java.util.Date
。尤其是后者,它总是设计得很糟糕,现在已经过时了。
我正在介绍方法。
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
我发现这段代码比执行除法和模运算的代码更易读。它效率不高,但对于 20 个案例中的 19 个以上,这应该没有问题。如果你喜欢除法,你当然也可以用 java.time 来做:
int year = intDate / 10000;
int monthDay = intDate % 10000;
int month = monthDay / 100;
int day = monthDay % 100;
LocalDate date = LocalDate.of(year, month, day);
如果您确实需要 java.util.Date 用于尚未升级到 java 的旧版 API。时间(或 Joda-Time),像这样转换(无论您使用上述哪种转换):
Instant asInstant = date.atStartOfDay(ZoneId.systemDefault()).toInstant();
Date oldfashionedDate = Date.from(asInstant);
System.out.println(oldfashionedDate);
当我的默认时区设置为Europe/Zaporozhye时的输出:
Sat Jun 21 01:00:00 EET 1930
(我们注意到由于转换到夏令时 (DST),我们得到 01:00:00。00:00:00 的时间在这个时区的这一天不存在。)
如果还在使用 Joda-Time
如果您仍在使用 Joda-Time,您自己使用 toDateTimeAtStartOfDay()
的答案就可以了。
PS 我用 Joda-Time 2.9.9 重现了你的问题,我的时区设置为 Europe/Zaporozhye 和你的整数 19300621(不知道你为什么把它作为十六进制,0x0126810d ).我遇到了与您类似的异常: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 解释如何使用 java.time.