0
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 日执行时,它运行良好。

文档说:

类中的setLenient(boolean leniency)方法Calendar用于指定对日期和时间的解释是否宽松。参数:该方法采用一个引用日历模式的类型的leniency参数。boolean

有什么解释吗?即使在我现在的本地,我也无法重现该问题。本地时间设置为 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)
`````````
4

2 回答 2

2

tl;博士

永远不要使用Calendar,现在是遗留的,被java.time类所取代,例如ZonedDateTime.

使用YearWeek来自ThreeTen-Extra项目的专用课程来跟踪标准ISO 8601 周

自定义格式化程序

定义一个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 标准格式-W,在基于周的年和周之间的中间插入一个。java.time类和ThreeTen -Extra类在解析/生成字符串时都默认使用 ISO 8601 格式。

String input = "202001";
String inputModified = input.substring( 0 , 4 ) + "-W" + input.substring( 4 );
YearWeek yearWeek = YearWeek.parse( inputModified ) ;

yearWeek.toString(): 2020-W01

避免遗留的日期时间类

不要浪费时间试图理解Calendar。这个可怕的类在几年前被JSR 310 中定义的现代java.time类所取代。

星期的定义

您必须指定一周的定义。你的意思是第 1 周包含一年中的第一天吗?还是第 1 周包含一周中的某一天?或者第 1 周是第一个完全由新年日期组成的日历周?或者也许是行业特定的周定义?其他定义?

令人困惑的事情之一Calendar是它对一周的定义改变了Locale。这是避免遗留类的众多原因之一。

以周为单位的年份

根据您对周的定义,一周中的年份可能不是该周某些日期的日历年。基于周的年份可能与日历年重叠。

标准周和基于周的年份

例如,标准 ISO 8601 周将一周定义为:

  • 从星期一开始,并且
  • 第 1 周包含日历年的第一个星期四。

因此,以每周为基础的一年中有 52 或 53 个整周。当然,这意味着上一个和/或下一个日历年的某些日期可能会出现在我们基于周的一年的第一周/最后几周。

org.threeten.extra.YearWeek

一个问题是你试图用一个代表时刻的类来表示一年一周,在时区的上下文中是一个带有一天中时间的日期。

相反,请使用专门构建的类。您可以在ThreeTen-Extra库中找到一个YearWeek。该库扩展了Java 8 及更高版本中内置的java.time类的功能。

使用该类,我认为我们可以定义 aDateTimeFormatter以使用格式模式解析您的输入,YYYYww其中YYYY表示基于周的 4 位数年份,ww表示两位数周数。像这样:

// FAIL
String input = "202001" ; 
DateTimeFormatter f = DateTimeFormatter.ofPattern( "YYYYww" ) ;
YearWeek yearWeek = YearWeek.parse( input , f ) ;

但是使用该格式化程序会引发DateTimeParseException我无法理解的原因。

线程“主”java.time.format.DateTimeParseException 中的异常:无法解析文本“202001”:无法从 TemporalAccessor 获取 YearWeek:{WeekOfWeekBasedYear[WeekFields[SUNDAY,1]]=1, WeekBasedYear[WeekFields[SUNDAY,1 ]]=2020},java.time.format.Parsed 类型的 ISO

…</p>

原因:java.time.DateTimeException:无法从 TemporalAccessor 获取 YearWeek:{WeekOfWeekBasedYear[WeekFields[SUNDAY,1]]=1, WeekBasedYear[WeekFields[SUNDAY,1]]=2020},java.time.format 类型的 ISO .已解析

…</p>

原因:java.time.temporal.UnsupportedTemporalTypeException:不支持的字段:WeekBasedYear

或者,我们可以使用从零件DateTimeFormatterBuilder构建一个。DateTimeFormatter通过仔细阅读Java 13OpenJDK源代码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" ) ;
于 2019-11-30T19:21:38.587 回答
2

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该类。那个类总是设计得很糟糕,现在已经过时了。相反,我使用的是现代 Java 日期和时间 API java.time。

有什么解释吗?

感谢 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 日的那一周。在将计算机时间设置为 2020 年 11 月 22 日并将时区设置为美国/芝加哥。没有发现异常(输出包括 Sun Jan 05 13:54:27 CST 2020)。我的语言环境遵循国际标准 ISO。星期一是一周的第一天,第 1 周是第一周至少有 4 天的新年。因此,2020 年的第 1 周是从 2019 年 12 月 30 日星期一到 1 月 5 日星期日。我想在星期一或星期二,我也可以在此语言环境中重现您的问题,我没有尝试过。

PS如何解析一个整数

只是提示,要将字符串解析为int,只需使用Integer.parseInt(yourString). 无需创建新Integer对象。

关联

Oracle 教程:日期时间解释如何使用 java.time。

于 2019-11-30T22:56:03.490 回答