Calendar.getTime 非法参数异常
Calendar.getTime IllegalArgumentException
import java.util.Calendar;
public class WeekYear {
static String input = "202001";
//static String format = "YYYYMM";
public static void main(String[] args) throws ParseException {
Calendar lCal = Calendar.getInstance();
System.out.println(lCal.isLenient());
lCal.setLenient(false);
lCal.set(Calendar.YEAR, new Integer(input.substring(0, 4)).intValue());
lCal.set(Calendar.WEEK_OF_YEAR, new Integer(input.substring(4, 6)).intValue());
//lCal.setMinimalDaysInFirstWeek(5);
System.out.println(lCal.isLenient());
System.out.println(lCal.getTime());
//lCal.set(Calendar.YEAR, new Integer(input.substring(0, 4)).intValue());
//lCal.set(Calendar.WEEK_OF_YEAR, new Integer(input.substring(4, 6)).intValue());
//System.out.println(lCal.getTime());
}
}
当此代码在 2020 年 11 月 22 日执行时,我从 Calendar.getTime() 得到一个 IllegalArgumentException。但是在 2020 年 11 月 27 日执行时它运行良好。
文档说:
The setLenient(boolean leniency)
method in Calendar
class is used to specify whether the interpretation of the date and time is to be lenient or not. Parameters: The method takes one parameter leniency
of the boolean
type that refers to the mode of the calendar.
有什么解释吗?我现在即使在本地也无法重现该问题。当地时间设置为 CST
异常堆栈:
Exception in thread "main" java.lang.IllegalArgumentException: year: 2020 -> 2019
at java.util.GregorianCalendar.computeTime(GregorianCalendar.java:2829)
at java.util.Calendar.updateTime(Calendar.java:3393)
at java.util.Calendar.getTimeInMillis(Calendar.java:1782)
at java.util.Calendar.getTime(Calendar.java:1755)
at WildDog.main(WildDog.java:13)
`````````
tl;博士
从不使用 Calendar
,现在是旧版,被 java.time class 取代,例如 ZonedDateTime
.
使用特制的 class、YearWeek
from the ThreeTen-Extra project, to track standard ISO 8601 weeks。
自定义格式化程序
定义一个 DateTimeFormatter
对象来匹配您的非标准输入字符串。
org.threeten.extra.YearWeek
.parse(
"202001" ,
new DateTimeFormatterBuilder()
.parseCaseInsensitive()
.appendValue( IsoFields.WEEK_BASED_YEAR, 4, 10, SignStyle.EXCEEDS_PAD)
.appendValue(IsoFields.WEEK_OF_WEEK_BASED_YEAR, 2)
.toFormatter()
)
.toString()
2020-W01
标准格式化程序
或者操纵您的输入字符串以符合 ISO 8601 standard format,在 week-based-year 和 week 之间插入一个 -W
。 java.time classes 和 ThreeTen-Extra classes 都使用 ISO 8601 格式当 parsing/generating 个字符串时默认。
String input = "202001";
String inputModified = input.substring( 0 , 4 ) + "-W" + input.substring( 4 );
YearWeek yearWeek = YearWeek.parse( inputModified ) ;
yearWeek.toString(): 2020-W01
避免遗留日期时间 classes
不要浪费时间去理解 Calendar
。这个可怕的 class 多年前被 JSR 310 中定义的现代 java.time classes 所取代。
周的定义
您必须指定您对一周的定义。您是说第 1 周包含一年的第一天吗?或者第 1 周包含一周中的某一天?或者第 1 周是第一个完全由新年日期组成的日历周?或者也许是行业特定的周定义?其他定义?
关于 Calendar
的一个令人困惑的事情是它对一周的定义改变了 Locale
。这是避免遗留问题的众多原因之一 class。
基于周的年份
根据您对周的定义,一周中的年份可能不是该周某些日期的日历年。基于周的年份可能与日历年重叠。
标准周和基于周的年份
例如,standard ISO 8601 week 将一周定义为:
- 星期一开始,并且
- 第 1 周包含日历年的第一个星期四。
所以每个基于周的年份有 52 或 53 个整周。当然,这意味着之前 and/or 之后日历年的某些日期可能会出现在我们基于周的年份的 first/last 周中。
org.threeten.extra.YearWeek
一个问题是您试图用 class 来表示年-周,该 class 表示时刻,在时区上下文中带有一天中的时间的日期。
相反,使用专门构建的 class。您可以在 ThreeTen-Extra library, YearWeek
中找到一个。该库扩展了 java.time class 内置于 Java 8 及更高版本中的功能。
有了 class 我认为我们可以定义一个 DateTimeFormatter
来使用格式模式 YYYYww
解析您的输入,其中 YYYY
表示 4 位数字year of week-based-year,ww
表示两位数的周数。像这样:
// FAIL
String input = "202001" ;
DateTimeFormatter f = DateTimeFormatter.ofPattern( "YYYYww" ) ;
YearWeek yearWeek = YearWeek.parse( input , f ) ;
但是使用那个格式化程序会抛出一个 DateTimeParseException
,原因让我无法理解。
Exception in thread "main" java.time.format.DateTimeParseException: Text '202001' could not be parsed: Unable to obtain YearWeek from TemporalAccessor: {WeekOfWeekBasedYear[WeekFields[SUNDAY,1]]=1, WeekBasedYear[WeekFields[SUNDAY,1]]=2020},ISO of type java.time.format.Parsed
…
Caused by: java.time.DateTimeException: Unable to obtain YearWeek from TemporalAccessor: {WeekOfWeekBasedYear[WeekFields[SUNDAY,1]]=1, WeekBasedYear[WeekFields[SUNDAY,1]]=2020},ISO of type java.time.format.Parsed
…
Caused by: java.time.temporal.UnsupportedTemporalTypeException: Unsupported field: WeekBasedYear
或者,我们可以使用 DateTimeFormatterBuilder
从零件构建 DateTimeFormatter
。通过仔细阅读 the OpenJDK source code for Java 13 for DateTimeFormatter.ISO_WEEK_DATE
,我能够拼凑出这个似乎有效的格式化程序。
DateTimeFormatter f =
new DateTimeFormatterBuilder()
.parseCaseInsensitive()
.appendValue( IsoFields.WEEK_BASED_YEAR, 4, 10, SignStyle.EXCEEDS_PAD)
.appendValue(IsoFields.WEEK_OF_WEEK_BASED_YEAR, 2)
.toFormatter()
;
使用那个:
String input = "202001" ;
YearWeek yearWeek = YearWeek.parse( input , f ) ;
ISO 8601
让您的数据发布者了解 ISO 8601 以文本形式表示日期时间值的标准定义格式。
要生成表示我们 YearWeek
值的标准格式字符串,请调用 toString
.
String output = yearWeek.toString() ;
2020-W01
并解析标准字符串。
YearWeek yearWeek = YearWeek.parse( "2020-W01" ) ;
Basil Bourque 已经提供了一个很好的答案。这是一个不需要外部依赖项的(前提是您使用的是 Java 8 或更高版本)。
java.time
WeekFields wf = WeekFields.of(Locale.US);
DateTimeFormatter yearWeekFormatter = new DateTimeFormatterBuilder()
.appendValue(wf.weekBasedYear(), 4)
.appendValue(wf.weekOfWeekBasedYear(), 2)
.parseDefaulting(ChronoField.DAY_OF_WEEK, DayOfWeek.SUNDAY.getValue())
.toFormatter();
String input = "202001";
LocalDate date = LocalDate.parse(input, yearWeekFormatter);
System.out.println(date);
输出为:
2019-12-29
假设星期日是一周的第一天,第 1 周是包含 1 月 1 日的那一周,这是正确的:2020 年的第 1 周从 2019 年 12 月 29 日星期日开始。如果您想要定义的周其他方式,只需使用不同的 WeekFields
对象。
我建议您不要使用 Calendar
class。 class 总是设计得很糟糕,现在已经过时了。相反,我使用 java.time,现代 Java 日期和时间 API。
有什么解释吗?
感谢 user85421 如何重现。您首先要创建一个代表当前日期的 Calendar
对象(实际上是 GregorianCalendar
的一个实例),在您的示例中,2020 年 11 月 22 日是星期日(显然已经将计算机时钟提前了将近一年)。然后,您将其年份设置为 2020 年(不变)并将其周数设置为 1。但是,正如我们在上面看到的,这会将日期更改为 2019 年 12 月 29 日,从而与您设置为 2020 年的年份发生冲突. 因此 GregorianCalendar
决定你在问不可能的事情并抛出异常。我得到的堆栈跟踪是:
java.lang.IllegalArgumentException: YEAR: 2020 -> 2019
at java.base/java.util.GregorianCalendar.computeTime(GregorianCalendar.java:2826)
at java.base/java.util.Calendar.updateTime(Calendar.java:3395)
at java.base/java.util.Calendar.getTimeInMillis(Calendar.java:1782)
at java.base/java.util.Calendar.getTime(Calendar.java:1755)
at ovv.misc.Test.main(Test.java:17)
在你的第二个例子中,你是 运行 你在 2020 年 11 月 27 日,一个星期五的程序。这次改成2020年1月3日星期五,所以还是2020年内,所以没有冲突,也就没有例外。
该解释假定您的默认语言环境是一年中的第 1 周定义为包含 1 月 1 日的那一周。在将我的计算机时间设置为 11 月之后,我运行 在我自己的语言环境中有您的代码2020 年 22 日,我的时区为 America/Chicago。未见异常(输出包括 Sun Jan 05 13:54:27 CST 2020)。我的语言环境遵循国际标准 ISO。星期一是一周的第一天,第 1 周是新年中至少有 4 天的第一周。所以 2020 年的第 1 周是从 2019 年 12 月 30 日星期一到 1 月 5 日星期日。我想在星期一或星期二我也可以在这个语言环境中重现你的问题,我没试过。
PS 如何解析整数
提示,要将字符串解析为 int
,只需使用 Integer.parseInt(yourString)
。无需创建新的 Integer
对象。
Link
Oracle tutorial: Date Time 解释如何使用 java.time.
import java.util.Calendar;
public class WeekYear {
static String input = "202001";
//static String format = "YYYYMM";
public static void main(String[] args) throws ParseException {
Calendar lCal = Calendar.getInstance();
System.out.println(lCal.isLenient());
lCal.setLenient(false);
lCal.set(Calendar.YEAR, new Integer(input.substring(0, 4)).intValue());
lCal.set(Calendar.WEEK_OF_YEAR, new Integer(input.substring(4, 6)).intValue());
//lCal.setMinimalDaysInFirstWeek(5);
System.out.println(lCal.isLenient());
System.out.println(lCal.getTime());
//lCal.set(Calendar.YEAR, new Integer(input.substring(0, 4)).intValue());
//lCal.set(Calendar.WEEK_OF_YEAR, new Integer(input.substring(4, 6)).intValue());
//System.out.println(lCal.getTime());
}
}
当此代码在 2020 年 11 月 22 日执行时,我从 Calendar.getTime() 得到一个 IllegalArgumentException。但是在 2020 年 11 月 27 日执行时它运行良好。
文档说:
The
setLenient(boolean leniency)
method inCalendar
class is used to specify whether the interpretation of the date and time is to be lenient or not. Parameters: The method takes one parameterleniency
of theboolean
type that refers to the mode of the calendar.
有什么解释吗?我现在即使在本地也无法重现该问题。当地时间设置为 CST
异常堆栈:
Exception in thread "main" java.lang.IllegalArgumentException: year: 2020 -> 2019
at java.util.GregorianCalendar.computeTime(GregorianCalendar.java:2829)
at java.util.Calendar.updateTime(Calendar.java:3393)
at java.util.Calendar.getTimeInMillis(Calendar.java:1782)
at java.util.Calendar.getTime(Calendar.java:1755)
at WildDog.main(WildDog.java:13)
`````````
tl;博士
从不使用 Calendar
,现在是旧版,被 java.time class 取代,例如 ZonedDateTime
.
使用特制的 class、YearWeek
from the ThreeTen-Extra project, to track standard ISO 8601 weeks。
自定义格式化程序
定义一个 DateTimeFormatter
对象来匹配您的非标准输入字符串。
org.threeten.extra.YearWeek
.parse(
"202001" ,
new DateTimeFormatterBuilder()
.parseCaseInsensitive()
.appendValue( IsoFields.WEEK_BASED_YEAR, 4, 10, SignStyle.EXCEEDS_PAD)
.appendValue(IsoFields.WEEK_OF_WEEK_BASED_YEAR, 2)
.toFormatter()
)
.toString()
2020-W01
标准格式化程序
或者操纵您的输入字符串以符合 ISO 8601 standard format,在 week-based-year 和 week 之间插入一个 -W
。 java.time classes 和 ThreeTen-Extra classes 都使用 ISO 8601 格式当 parsing/generating 个字符串时默认。
String input = "202001";
String inputModified = input.substring( 0 , 4 ) + "-W" + input.substring( 4 );
YearWeek yearWeek = YearWeek.parse( inputModified ) ;
yearWeek.toString(): 2020-W01
避免遗留日期时间 classes
不要浪费时间去理解 Calendar
。这个可怕的 class 多年前被 JSR 310 中定义的现代 java.time classes 所取代。
周的定义
您必须指定您对一周的定义。您是说第 1 周包含一年的第一天吗?或者第 1 周包含一周中的某一天?或者第 1 周是第一个完全由新年日期组成的日历周?或者也许是行业特定的周定义?其他定义?
关于 Calendar
的一个令人困惑的事情是它对一周的定义改变了 Locale
。这是避免遗留问题的众多原因之一 class。
基于周的年份
根据您对周的定义,一周中的年份可能不是该周某些日期的日历年。基于周的年份可能与日历年重叠。
标准周和基于周的年份
例如,standard ISO 8601 week 将一周定义为:
- 星期一开始,并且
- 第 1 周包含日历年的第一个星期四。
所以每个基于周的年份有 52 或 53 个整周。当然,这意味着之前 and/or 之后日历年的某些日期可能会出现在我们基于周的年份的 first/last 周中。
org.threeten.extra.YearWeek
一个问题是您试图用 class 来表示年-周,该 class 表示时刻,在时区上下文中带有一天中的时间的日期。
相反,使用专门构建的 class。您可以在 ThreeTen-Extra library, YearWeek
中找到一个。该库扩展了 java.time class 内置于 Java 8 及更高版本中的功能。
有了 class 我认为我们可以定义一个 DateTimeFormatter
来使用格式模式 YYYYww
解析您的输入,其中 YYYY
表示 4 位数字year of week-based-year,ww
表示两位数的周数。像这样:
// FAIL
String input = "202001" ;
DateTimeFormatter f = DateTimeFormatter.ofPattern( "YYYYww" ) ;
YearWeek yearWeek = YearWeek.parse( input , f ) ;
但是使用那个格式化程序会抛出一个 DateTimeParseException
,原因让我无法理解。
Exception in thread "main" java.time.format.DateTimeParseException: Text '202001' could not be parsed: Unable to obtain YearWeek from TemporalAccessor: {WeekOfWeekBasedYear[WeekFields[SUNDAY,1]]=1, WeekBasedYear[WeekFields[SUNDAY,1]]=2020},ISO of type java.time.format.Parsed
…
Caused by: java.time.DateTimeException: Unable to obtain YearWeek from TemporalAccessor: {WeekOfWeekBasedYear[WeekFields[SUNDAY,1]]=1, WeekBasedYear[WeekFields[SUNDAY,1]]=2020},ISO of type java.time.format.Parsed
…
Caused by: java.time.temporal.UnsupportedTemporalTypeException: Unsupported field: WeekBasedYear
或者,我们可以使用 DateTimeFormatterBuilder
从零件构建 DateTimeFormatter
。通过仔细阅读 the OpenJDK source code for Java 13 for DateTimeFormatter.ISO_WEEK_DATE
,我能够拼凑出这个似乎有效的格式化程序。
DateTimeFormatter f =
new DateTimeFormatterBuilder()
.parseCaseInsensitive()
.appendValue( IsoFields.WEEK_BASED_YEAR, 4, 10, SignStyle.EXCEEDS_PAD)
.appendValue(IsoFields.WEEK_OF_WEEK_BASED_YEAR, 2)
.toFormatter()
;
使用那个:
String input = "202001" ;
YearWeek yearWeek = YearWeek.parse( input , f ) ;
ISO 8601
让您的数据发布者了解 ISO 8601 以文本形式表示日期时间值的标准定义格式。
要生成表示我们 YearWeek
值的标准格式字符串,请调用 toString
.
String output = yearWeek.toString() ;
2020-W01
并解析标准字符串。
YearWeek yearWeek = YearWeek.parse( "2020-W01" ) ;
Basil Bourque 已经提供了一个很好的答案。这是一个不需要外部依赖项的(前提是您使用的是 Java 8 或更高版本)。
java.time
WeekFields wf = WeekFields.of(Locale.US);
DateTimeFormatter yearWeekFormatter = new DateTimeFormatterBuilder()
.appendValue(wf.weekBasedYear(), 4)
.appendValue(wf.weekOfWeekBasedYear(), 2)
.parseDefaulting(ChronoField.DAY_OF_WEEK, DayOfWeek.SUNDAY.getValue())
.toFormatter();
String input = "202001";
LocalDate date = LocalDate.parse(input, yearWeekFormatter);
System.out.println(date);
输出为:
2019-12-29
假设星期日是一周的第一天,第 1 周是包含 1 月 1 日的那一周,这是正确的:2020 年的第 1 周从 2019 年 12 月 29 日星期日开始。如果您想要定义的周其他方式,只需使用不同的 WeekFields
对象。
我建议您不要使用 Calendar
class。 class 总是设计得很糟糕,现在已经过时了。相反,我使用 java.time,现代 Java 日期和时间 API。
有什么解释吗?
感谢 user85421 如何重现。您首先要创建一个代表当前日期的 Calendar
对象(实际上是 GregorianCalendar
的一个实例),在您的示例中,2020 年 11 月 22 日是星期日(显然已经将计算机时钟提前了将近一年)。然后,您将其年份设置为 2020 年(不变)并将其周数设置为 1。但是,正如我们在上面看到的,这会将日期更改为 2019 年 12 月 29 日,从而与您设置为 2020 年的年份发生冲突. 因此 GregorianCalendar
决定你在问不可能的事情并抛出异常。我得到的堆栈跟踪是:
java.lang.IllegalArgumentException: YEAR: 2020 -> 2019
at java.base/java.util.GregorianCalendar.computeTime(GregorianCalendar.java:2826)
at java.base/java.util.Calendar.updateTime(Calendar.java:3395)
at java.base/java.util.Calendar.getTimeInMillis(Calendar.java:1782)
at java.base/java.util.Calendar.getTime(Calendar.java:1755)
at ovv.misc.Test.main(Test.java:17)
在你的第二个例子中,你是 运行 你在 2020 年 11 月 27 日,一个星期五的程序。这次改成2020年1月3日星期五,所以还是2020年内,所以没有冲突,也就没有例外。
该解释假定您的默认语言环境是一年中的第 1 周定义为包含 1 月 1 日的那一周。在将我的计算机时间设置为 11 月之后,我运行 在我自己的语言环境中有您的代码2020 年 22 日,我的时区为 America/Chicago。未见异常(输出包括 Sun Jan 05 13:54:27 CST 2020)。我的语言环境遵循国际标准 ISO。星期一是一周的第一天,第 1 周是新年中至少有 4 天的第一周。所以 2020 年的第 1 周是从 2019 年 12 月 30 日星期一到 1 月 5 日星期日。我想在星期一或星期二我也可以在这个语言环境中重现你的问题,我没试过。
PS 如何解析整数
提示,要将字符串解析为 int
,只需使用 Integer.parseInt(yourString)
。无需创建新的 Integer
对象。
Link
Oracle tutorial: Date Time 解释如何使用 java.time.