如何从给定季度获取月份

How to get Months from a given quarter

我正在尝试获取某个季度的月数。使用下面的代码,我成功地从 LocalDate.now() 实例中获取了当前季度的月份名称。

如何从一个季度字符串(例如 "Q1")中得到一个季度的月份?

int monthInt = Month.from(LocalDate.now()).firstMonthOfQuarter().getValue();
      for (int j = 1; j <= 3; j++) { //for each month in quarter
          System.out.println(Month.of(monthInt).name()); //January, February, March
          monthInt++;
      }

对于quarter > firstmonth,规则是

Q1 -> 1
Q2 -> 4
Q3 -> 7
Q4 -> 10

With math : quarterValue *3 -2

所以在 Java 中应用这个:

String quarter = "Q1";
int monthInt = Integer.parseInt(quarter.replaceAll("\D", "")) * 3 - 2;
for (int j = 0; j < 3; j++) {
    System.out.println(Month.of(monthInt + j).name());
}

List<String> quarters = Arrays.asList("Q1", "Q2", "Q3", "Q4");
for (String quarter : quarters) {
    System.out.print(quarter);
    int monthInt = Integer.parseInt(quarter.replaceAll("\D", "")) * 3 - 2;
    for (int j = 0; j < 3; j++) {
        System.out.print(" " + Month.of(monthInt + j).name());
    }
    System.out.println();
}

Q1 JANUARY FEBRUARY MARCH
Q2 APRIL MAY JUNE
Q3 JULY AUGUST SEPTEMBER
Q4 OCTOBER NOVEMBER DECEMBER

我们可以通过查看 IsoFields.QUARTER_OF_YEARgetFrom 的声明来了解 JDK 是如何计算季度的:

public long getFrom(TemporalAccessor temporal) {
    if (isSupportedBy(temporal) == false) {
        throw new UnsupportedTemporalTypeException("Unsupported field: QuarterOfYear");
    }
    long moy = temporal.getLong(MONTH_OF_YEAR);
    return ((moy + 2) / 3);
}

注意它如何使用公式 quarter = (moy + 2) / 3。因此,要找到一个季度的起始月份,我们只需要根据 moy - moy = quarter * 3 - 2.

重新排列即可

你可以这样写一个方法:

private static List<String> monthNamesFromQuarter(int quarter) {
    // you can do the validation of quarter yourself
    int start = quarter * 3 - 2;
    return IntStream.range(start, start + 3)
            .mapToObj(Month::of)
            .map(Month::name)
            .collect(Collectors.toList());
}

我建议我们构建每个季度中包含的月份的地图,并在需要某个季度字符串的月份时进行简单的地图查找。流管道和足够的 DateTimeFormatter 将在几行内构建这样的地图:

static Map<String, List<Month>> monthsPerQuarter = Arrays.stream(Month.values())
        .collect(Collectors.groupingBy(
                DateTimeFormatter.ofPattern("QQQ", Locale.ENGLISH)::format));

IMO 这里的最大优势是我们不需要自己做数学运算。虽然从季度数字到月份数字的数学转换对于编写它的人来说似乎很简单,但它不仅容易出错,而且对于许多读者来说也不清楚和难以理解。

现在我们可以像这样查找示例:

    System.out.println(monthsPerQuarter.get("Q3"));

输出为:

[JULY, AUGUST, SEPTEMBER]

如果您需要单独的月份:

    monthsPerQuarter.get("Q4").forEach(System.out::println);
OCTOBER
NOVEMBER
DECEMBER

如果您的四分之一最初是 1 到 4 范围内的数字,您可以改用这张地图:

static Map<Integer, List<Month>> monthsPerQuarter = Arrays.stream(Month.values())
        .collect(Collectors.groupingBy(m -> m.get(IsoFields.QUARTER_OF_YEAR)));

我推荐 map 方法的一个原因是:从 Month 转换为季度字符串很容易,但是 java.time 没有提供从季度字符串转换为月份的简单方法。只是为了演示而不是我推荐的方式,有人可能会这样做:

    DateTimeFormatter quarterFormatter = new DateTimeFormatterBuilder()
            .appendPattern("QQQ")
            // Any year will do since all years have the same 4 quarters @ the same 3 months
            .parseDefaulting(ChronoField.YEAR, 2000)
            .parseDefaulting(IsoFields.DAY_OF_QUARTER, 1)
            .toFormatter(Locale.ENGLISH);

    String qString = "Q1";
    Month firstMonthOfQuarter = Month.from(quarterFormatter.parse(qString));
    IntStream.range(0, 3)
            .mapToObj(firstMonthOfQuarter::plus)
            .forEach(System.out::println);
JANUARY
FEBRUARY
MARCH

这是 11 行代码,而不是 4 行,我看不出有什么收获。

tl;博士

使用org.threeten.extra.YearQuarter class, along with Quarter, ZoneId, LocalDate, and Month.

YearQuarter                         // Represent an entire quarter of a specific year.
.now( ZoneId.of( "Asia/Tokyo" ) )   // Determine the current quarter as seen via the wall-clock time used by the people of a particular region (a time zone). 
.with(                              // Move to another quarter.
    Quarter.valueOf( "Q1" )         // Or, `Quarter.of( 1 )` if starting with an integer number rather than a `String` object.
)                                   // Returns another `YearQuarter` object, rather than modifying the original.
.atDay( 1 )                         // Returns a `LocalDate` object.
.getMonth()                         // Returns a `Month` enum object.
.getDisplayName(                    // Automatically localize the name of the month. 
    TextStyle.FULL ,                // How long or abbreviated do you want the translation.
    Locale.US                       // Or Locale.CANADA_FRENCH and so on. 
)                                   // Returns a `String` object.

January

YearQuarter三十番外

ThreeTen-Extra library has a class you might find useful for this work: YearQuarter

获取当前季度。我们需要一个时区来确定当前日期,从而确定当前季度。对于任何给定时刻,全球各地的日期因时区而异。

ZoneId z = ZoneId.of( "Africa/Tunis" ) ;
YearQuarter currentYearQuarter = YearQuarter.now( z ) ;

但是您想通过解析字符串来确定四分之一。

如果您的字符串样式类似于 ISO 8601 (the standard does not actually specify quarters) YYYY-Qq then YearQuarter can directly parse

String input = "2020-Q1" ;
YearQuarter yearQuarter = YearQuarter.parse( input ) ;

如果只有季度部分没有年份,请使用 Quarter enum. If your input string is Q1 and such, use valueOf 检索匹配的枚举对象。

String input = "Q1" ;
Quarter quarter = Quarter.valueOf( input ) ;

如果你有数字而不是字符串,即1234,则使用静态方法Quarter.of.顺便说一句,在您自己的代码中,您应该传递这些 Quarter 对象,而不仅仅是一个整数或字符串,以使您的代码更加自我记录,提供类型安全,并确保有效值。

int input = 1 ; // Domain: 1, 2, 3, 4. 
Quarter quarter = Quarter.of( input ) ;

将那个 Quarter 实例应用到我们当前的 YearQuarter 实例以获得另一个 YearQuarter 实例。这些 类 使用 immutable objects 模式,因此我们不会修改现有实例,而是生成新实例。

YearQuarter yearQuarter = currentYearQuarter.with( quarter ) ;

yearQuarter.toString(): 2019-Q1

获取第一个日期(LocalDate), and year-month (YearMonth), and Month 枚举对象,来自该年季度。

LocalDate firstDate = yearQuarter.atDay( 1 ) ;
YearMonth yearMonth1 = YearMonth.from( firstDate ) ;
YearMonth yearMonth2 = yearMonth1.plusMonths( 1 ) ;
YearMonth yearMonth3 = yearMonth1.plusMonths( 2 ) ;

生成包含 automatically localized 月份名称的字符串。

Locale locale = Locale.US ;  // Or Locale.CANADA_FRENCH and so on. 
String output1 = yearMonth1.getMonth().getDisplayName( TextStyle.FULL , locale ) ;

January


关于java.time

java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.

要了解更多信息,请参阅 Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310

Joda-Time project, now in maintenance mode, advises migration to the java.time 类.

您可以直接与数据库交换 java.time 对象。使用 JDBC driver compliant with JDBC 4.2 或更高版本。不需要字符串,不需要 java.sql.* 类.

在哪里获取java.time类?

ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.