我有这个功能:
/**
* @return From november to december -> current year +1 else current year
*/
public static int getCurrentScolYear () {
int month = Calendar.getInstance().get(Calendar.MONTH);
if ( month == NOVEMBER || month == DECEMBER) {
return Calendar.getInstance().get(Calendar.YEAR) +1;
}
return Calendar.getInstance().get(Calendar.YEAR);
}
这用于计算自11月开始的索偿年度。
我想通过更改“当前时间”在Junit测试中对此进行测试。所以我可以测试不同日期的结果。
我找到了这个线程,但似乎太复杂了:java: how to mock Calendar.getInstance()?
有没有简单的解决方案可以对此进行测试?
答案 0 :(得分:2)
可怕的Calendar
类是几年前被 java.time 类(特别是ZonedDateTime
)取代的。没有理由再次使用Calendar
。
以下是我替换您的代码。
我们可以返回类型安全且自解释的java.time.Year
对象,而不是单纯的整数。但这会产生误导,因为该类被明确定义为符合ISO 8601的年份。
我建议您定义自己的AcademicYear
类。传递此类的对象可以使您的代码更具自记录性,提供类型安全性,并确保有效值。类似于以下课程。
此自定义类还为您的静态方法提供了一个家。
该课程紧随java.time.Year
课程的领导,包括its naming conventions。
一个学年实际上是两年年的数字,即开始年和随后的停止年。下面的类将两者存储。
示例用法:
AcademicYear.now().getDisplayName() // 2017-2018
或
AcademicYear.now().getValueStart() // First year of the two-year range, such as 2018 of the year 2018-2019.
我把这堂课放在一起,没有测试。因此,请以此为指导,而不是生产代码。
AcademicYear.java
package com.basilbourque.example;
import java.time.*;
import java.util.EnumSet;
import java.util.Objects;
import java.util.Set;
// This class follows the lead of the `java.time.Year` class.
// See JavaDoc: https://docs.oracle.com/javase/10/docs/api/java/time/Year.html
// See OpenJDK source-code: http://hg.openjdk.java.net/jdk10/master/file/be620a591379/src/java.base/share/classes/java/time/Year.java
// This class follows the immutable objects pattern. So only getter accessor methods, no setters.
public class AcademicYear {
// Statics
static public int FIRST_YEAR_LIMIT = 2017;
static public int FUTURE_YEARS_LIMIT = 10;
static public Set < Month > academicYearStartingMonths = EnumSet.of( Month.NOVEMBER , Month.DECEMBER );
// Members
private int academicYearNumberStart;
// Constructor
private AcademicYear ( int academicYearNumberStart ) {
if ( academicYearNumberStart < AcademicYear.FIRST_YEAR_LIMIT ) {
throw new IllegalArgumentException( "Received a year number: " + academicYearNumberStart + " that is too long ago (before " + AcademicYear.FIRST_YEAR_LIMIT + "). Message # c5fd65c1-ed10-4fa1-96db-77d08ef1d97e." );
}
if ( academicYearNumberStart > Year.now().getValue() + AcademicYear.FUTURE_YEARS_LIMIT ) {
throw new IllegalArgumentException( "Received a year number that is too far in the future, over " + AcademicYear.FUTURE_YEARS_LIMIT + " away. Message # 8581e4ca-afb3-4ab7-8849-9b02c434eb4c." );
}
this.academicYearNumberStart = academicYearNumberStart;
}
public static AcademicYear of ( int academicYearNumberStart ) {
return new AcademicYear( academicYearNumberStart );
}
public int getValueStart ( ) {
return this.academicYearNumberStart;
}
public int getValueStop ( ) {
return ( this.academicYearNumberStart + 1 );
}
public String getDisplayName ( ) {
String s = this.academicYearNumberStart + "-" + ( this.academicYearNumberStart + 1 );
return s;
}
// ------| `Object` |---------------------
@Override
public String toString ( ) {
return "AcademicYear{ " +
"academicYearNumberStart=" + academicYearNumberStart +
" }";
}
@Override
public boolean equals ( Object o ) {
if ( this == o ) return true;
if ( o == null || getClass() != o.getClass() ) return false;
AcademicYear that = ( AcademicYear ) o;
return this.getDisplayName().equals( that.getDisplayName() );
}
@Override
public int hashCode ( ) {
return Objects.hash( this.getDisplayName() );
}
// -----------| Factory methods |-------------------
static public AcademicYear now ( ) { // I think making ZoneId optional is a poor design choice, but I do so here to mimic `java.time.Year`.
AcademicYear ay = AcademicYear.now( Clock.systemDefaultZone() );
return ay;
}
static public AcademicYear now ( ZoneId zoneId ) {
// Determine the current date as seen in the wall-clock time used by the people of a particular region (a time zone).
AcademicYear ay = AcademicYear.now( Clock.system( zoneId ) );
return ay;
}
static public AcademicYear now ( Clock clock ) {
final LocalDate localDate = LocalDate.now( clock );
AcademicYear ay = AcademicYear.from( localDate );
return ay;
}
static public AcademicYear from ( LocalDate localDate ) {
Objects.requireNonNull( localDate , "Received a null `LocalDate` object. Message # 558dd5e8-5cff-4c6e-b0f8-40dbcd76a753." );
// Extract the month of the specified date. If not Nov or Dec, subtract one from the year.
int y = localDate.getYear();
// If not November or December, subtract 1.
int startingYear;
if ( ! academicYearStartingMonths.contains( localDate.getMonth() ) ) {
startingYear = ( y - 1 );
} else {
startingYear = y;
}
AcademicYear ay = AcademicYear.of( startingYear );
return ay;
}
}
ZoneId
另一个更改是时区ZoneId
的必需参数。
时区对于确定日期(因此决定年份)至关重要。在任何给定时刻,日期都会在全球范围内变化。例如,Paris France午夜之后的几分钟是新的一天,而Montréal Québec仍然是“昨天”。
如果未指定时区,则JVM隐式应用其当前的默认时区。该默认值可能在运行时(!)期间change at any moment,因此您的结果可能会有所不同。最好将desired/expected time zone明确指定为参数。
以continent/region
的格式指定proper time zone name,例如America/Montreal
,Africa/Casablanca
或Pacific/Auckland
。切勿使用2-4个字母的缩写,例如EST
或IST
,因为它们不是真正的时区,不是标准化的,甚至不是唯一的(!)。
另一个变化是,我们有两组方法,而不是一个方法。
一种方法AcademicYear.from
对进行11月调整的逻辑负责。您将LocalDate
传递给此方法。
其他方法,AcademicYear.now
的三个变体,负责确定当前时刻,调整到指定的时区并提取仅日期的LocalDate
对象。然后将该LocalDate
对象传递给另一个方法。
Clock
用于单元测试对于测试,您可以将Clock
实例传递给now
方法变体之一,该实例可以根据需要更改时间。 Clock
类为多个时钟提供了更改的行为,例如定点时间,当前时刻加/减时间跨度以及更改的节奏,其中时钟按您指定的粒度滴答(例如每5分钟) 。
有关更改的Clock
行为的更多信息,请参见my Answer到另一个问题。
示例:
ZoneId z = ZoneId.of( "Africa/Tunis" ) ;
LocalDate ld = LocalDate.of( 2018 , Month.JANUARY , 23 ) ; // 2018-01-23
LocalTime lt = LocalTime.of( 8 , 0 ) ; // 8 AM.
Instant instant = ZonedDateTime.of( ld , lt , z ).toInstant() ;
Clock c = Clock.fixed( instant , z ) ;
AcademicYear ay = AcademicYear.now( c ) ;
java.time框架已内置在Java 8及更高版本中。这些类取代了麻烦的旧legacy日期时间类,例如java.util.Date
,Calendar
和SimpleDateFormat
。
目前位于Joda-Time的maintenance mode项目建议迁移到java.time类。
要了解更多信息,请参见Oracle Tutorial。并在Stack Overflow中搜索许多示例和说明。规格为JSR 310。
您可以直接与数据库交换 java.time 对象。使用符合JDBC driver或更高版本的JDBC 4.2。不需要字符串,不需要java.sql.*
类。
在哪里获取java.time类?
ThreeTen-Extra项目使用其他类扩展了java.time。该项目为将来可能在java.time中添加内容提供了一个试验场。您可能会在这里找到一些有用的类,例如Interval
,YearWeek
,YearQuarter
和more。