正如我已经解释过的那样,当用户在JXDatePicker中编辑日期时,他可以选择,是否以相同的格式再次键入,默认情况下是dd.MM.yyyy或只是dd.MM.yy。当他使用缩写形式时,我希望Picker选择当前世纪。
示例:
27.01.2012 edited to 27.01.10 should result in 27.01.2010
以及:
27.01.2012 edited to 27.01.2010 should also result in 27.01.2010
默认情况下,JXDatePicker以以下方式处理它:
27.01.2012 edited to 27.01.10 results in 27.01.0010
这并不是我想要的工作方式。经过一些简短的研究,我在SimpleDateFormat 中找到了以下方法
/**
* Sets the 100-year period 2-digit years will be interpreted as being in
* to begin on the date the user specifies.
*
* @param startDate During parsing, two digit years will be placed in the range
* <code>startDate</code> to <code>startDate + 100 years</code>.
*/
public void set2DigitYearStart(Date startDate)
乍一看,这听起来正是我所需要的。所以我测试了一下,但不幸的是,它并没有像我希望的那样工作。这是因为我想使用dd.MM.yyyy作为显示日期的格式,也希望它在编辑模式下像这样显示。例如,当用户点击2012年1月27日这样的日期时,我也希望它在编辑模式下也是这样,而不仅仅是缩写形式:2012年1日27日。
我现在的问题是,set2DigitYearStart(Date)不幸地只在我选择在编辑模式中使用缩写形式时有效。我做了一个小例子来展示这种情况(SwingX库是必需的,因为jxdatepicker,可以在这里找到)。
public class DatePickerExample extends JPanel
{
static JFrame frame;
public DatePickerExample()
{
JXDatePicker picker = new JXDatePicker();
JTextField field = new JTextField( 10 );
add( field );
add( picker );
final Calendar instance = Calendar.getInstance();
instance.set( 2012, 01, 26 );
Date date = instance.getTime();
picker.setDate( date );
// SimpleDateFormat format = new SimpleDateFormat( "dd.MM.yy" );//Works, but I wonna display and edit it with dd.MM.yyyy
SimpleDateFormat format = new SimpleDateFormat( "dd.MM.yyyy" );
final Date startDate = new Date( 0 );//01.01.1970
format.set2DigitYearStart( startDate );
picker.setFormats( format );
}
public static void main( String[] args )
{
frame = new JFrame();
frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
frame.setBounds( 400, 400, 400, 400 );
frame.setLayout( new BorderLayout() );
frame.add( new DatePickerExample() );
frame.setVisible( true );
}
}
有人已经有了同样的要求,可以告诉我如何让它发挥作用吗?欢迎提出任何想法。事先非常感谢。ymene
最终(希望:)
第一次编辑总结:
- DatePickerFormatter已经实现了一个查找策略(或@Robin建议的CompoundFormat)
- 用于解析的查找序列可由客户端代码配置
- 这个想法是尝试从第一个开始解析(通常是"最长的"),如果失败,则尝试下一个(通常不是很长的),以此类推,直到成功或抛出parseException
- 对于年份分析,SimpleDateFormat的规则与最长的第一次查找冲突:它要求在"yyyy"之前尝试"yy"
- 在datePicker中这样做会产生不必要的副作用,即总是以短年格式显示日期
原因是DatePickerFormatter:它不允许指定格式格式(只使用第一个)。出路是一个自定义的DatePickerFormatter,它支持它(在代码片段中,它被硬编码为使用第二个):
SimpleDateFormat longFormat = new SimpleDateFormat( "dd.MM.yyyy" );
SimpleDateFormat shortFormat = new SimpleDateFormat( "dd.MM.yy" );
Date startDate = new Date( 0 );//01.01.1970
shortFormat.set2DigitYearStart( startDate );
DatePickerFormatter formatter = new DatePickerFormatter(
// invers sequence for parsing to satisfy the year parsing rules
new DateFormat[] {shortFormat, longFormat}) {
@Override
public String valueToString(Object value) throws ParseException {
if (value == null) return null;
return getFormats()[1].format(value);
}
} ;
DefaultFormatterFactory factory = new DefaultFormatterFactory(formatter );
picker.getEditor().setFormatterFactory(factory);
不完全确定我们是否应该支持在基类中配置格式化程序。DatePickerFormatter是一个有点奇怪的野兽,它没有扩展InternalFormatter,并且查找过程有点与FormatterFactory竞争。。。
原始
它并不是以这种方式处理它的datePicker,而是核心格式(正如D1e已经指出的那样)。没有一种默认格式/ter/s同时支持两种格式:要查看,请尝试使用核心JFormatedTextField:-)来实现您的目标
解决方法可能是FormatterFactory:它允许根据上下文使用不同的格式:显示和编辑-后者在字段聚焦时使用,前者在所有其他时间使用。由于选择器的编辑器是一个JFormattedTextField,您可以直接配置它(而不是使用setFormats方法)
SimpleDateFormat format = new SimpleDateFormat( "dd.MM.yyyy" );
SimpleDateFormat editFormat = new SimpleDateFormat( "dd.MM.yy" );
final Date startDate = new Date( 0 );//01.01.1970
instance.setTime(startDate);
editFormat.set2DigitYearStart( instance.getTime() );
DefaultFormatterFactory factory = new DefaultFormatterFactory(
new DatePickerFormatter(new DateFormat[] {format}),
new DatePickerFormatter(new DateFormat[] {format}),
new DatePickerFormatter(new DateFormat[] {editFormat})
);
picker.getEditor().setFormatterFactory(factory);
编辑
读了Robin最近的答案(+1!)后,我大吃一惊——多年后,令人尴尬的是,我终于明白了SwingX的DatePickerFormatter试图做什么:那就是支持一个格式化程序的查找链(从长到短),提交后使用的时间最长,用户输入时使用的时间越短。
不幸的是,这并没有像直觉预期的那样奏效。给定一系列格式,从长到短(并根据世纪进行适当配置):
"yyyy", "yy"
和给定的输入
"10"
感觉就像是从第一名传给第二名,导致
2010
但事实并非如此。如SimpleDateFormat 中记录的(谁读文档…懒我,咳嗽…)
Year:[…]对于解析,如果模式字母的数量超过2,则无论数字的数量如何,都会从字面上解释年份。因此,使用模式"MM/dd/yyyy","01/11/12"解析为公元12年1月11日。
最后,由于DatePickerFormatter试图支持该查找,但没有成功,这可能被认为是SwingX问题,毕竟:-)
我不太清楚JXDatePicker的具体情况,但如果您想要模拟的具体功能是:用户输入27.01.2010和27.01.10应分别产生27.01.2010
然后这将工作:
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class Main {
public static void main(String[] args) throws ParseException {
String inputLiteralDateYY = "27.01.10"; //Also works with "27.01.97"
String inputLiteralDateYYYY = "27.01.2010"; //Also works with "27.01.1997"
DateFormat dfYYYY = new SimpleDateFormat("dd.MM.yyyy");
DateFormat dfYY = new SimpleDateFormat("dd.MM.yy");
Date dateFromYY = dfYY.parse(inputLiteralDateYY);
Date dateFromYYYY = dfYY.parse(inputLiteralDateYYYY);
String outputLiteralDateFromYY = dfYYYY.format(dateFromYY);
String outputLiteralDateFromYYYY = dfYYYY.format(dateFromYYYY);
System.out.println(outputLiteralDateFromYY);
System.out.println(outputLiteralDateFromYYYY);
}
}
问题是首先用"dd.MM.yy"模式解析输入,然后用"dd.MM.yyyy"模式格式返回。
希望这对你的场景有所帮助。
kleopatra已经解释了如何在日期选择器上设置Format
。对于这个用例,我将应用CompositeFormat
和ParseAllFormat
的组合,而不是使用单独的格式进行编辑和常规模式,以避免在开始编辑时更改String
(正如您已经注意到的)。
复合格式
顾名思义,复合格式是Format
类的复合实现,但仅用于解析。对于格式化,它使用一个Format
。这允许用户以多种形式输入他/她的日期,同时通过使用一种特定的格式来格式化日期。
您也可以通过编写一个更复杂的Format
来获得这种行为。但在这种情况下,只使用JDK的SimpleDateFormat
类提供的格式化/解析功能会更容易。
import java.text.FieldPosition;
import java.text.Format;
import java.text.ParsePosition;
import java.util.ArrayList;
import java.util.List;
/**
* <p>Composite form of {@link java.text.Format Format}. It uses multiple formats for parsing, and
* only one format for formatting.</p>
*
* <p>A possible use-case is the formatting of user input (e.g. in a {@code JFormattedTextField}).
* Multiple formats for parsing allows accepting multiple forms of user input without having to
* write a complicated format.</p>
*/
public class CompositeFormat extends Format {
private List<Format> fFormats = new ArrayList<>();
private Format fFormattingFormat;
/**
* Create a new
*/
public CompositeFormat() {
}
/**
* Add a format to this composite format
*
* @param aFormat The format to add
*/
public void addFormat( Format aFormat ) {
assertNotNull( aFormat, "You cannot add a null Format" );
if ( !( fFormats.contains( aFormat ) ) ) {
fFormats.add( aFormat );
}
}
/**
* Remove a format from this composite format
*
* @param aFormat The format to remove
*/
public void removeFormat( Format aFormat ) {
assertNotNull( aFormat, "You cannot remove a null Format" );
fFormats.remove( aFormat );
updateFormattingFormat();
}
/**
* Sets <code>aFormat</code> as the format which will be used for formatting the
* objects. The format will also be added to the list of available formats.
* @param aFormat The format which will be used for formatting
*/
public void setFormattingFormat( Format aFormat ){
assertNotNull( aFormat, "Formatting format may not be null" );
addFormat( aFormat );
fFormattingFormat = aFormat;
}
private void assertNotNull( Object aObjectToCheck, String aMessage ) {
if ( aObjectToCheck == null ) {
throw new NullPointerException( aMessage );
}
}
private void updateFormattingFormat(){
if ( !( fFormats.contains( fFormattingFormat ) ) ){
fFormattingFormat = null;
if ( !( fFormats.isEmpty() ) ){
fFormattingFormat = fFormats.iterator().next();
}
}
}
@Override
public StringBuffer format( Object obj, StringBuffer toAppendTo, FieldPosition pos ) {
assertNotNull( fFormattingFormat, "Set a formatting format before using this format" );
return fFormattingFormat.format( obj, toAppendTo, pos );
}
@Override
public Object parseObject( String source, ParsePosition pos ) {
if ( fFormats.isEmpty() ){
throw new UnsupportedOperationException( "Add at least one format before using this composite format" );
}
Format formatToUse = fFormats.iterator().next();
int maxIndex = pos.getIndex();
for ( Format format : fFormats ) {
ParsePosition tempPos = new ParsePosition( pos.getIndex() );
tempPos.setErrorIndex( pos.getErrorIndex() );
format.parseObject( source, tempPos );
if ( tempPos.getIndex() > maxIndex ){
maxIndex = tempPos.getIndex();
formatToUse = format;
if( maxIndex == source.length() ){
//found a format which parses the whole string
break;
}
}
}
return formatToUse.parseObject( source, pos );
}
}
ParseAllFormat
通常,对于用户输入,您希望可以格式化/解析整个用户输入,以避免用户输入半正确的字符串。ParseAllFormat
是常规Format
的装饰器,当只能解析String
的一部分时,它会抛出ParseException
s。
import java.text.AttributedCharacterIterator;
import java.text.FieldPosition;
import java.text.Format;
import java.text.ParseException;
import java.text.ParsePosition;
/**
* <p>Decorator for a {@link Format Format} which only accepts values which can be completely parsed
* by the delegate format. If the value can only be partially parsed, the decorator will refuse to
* parse the value.</p>
*/
public class ParseAllFormat extends Format {
private final Format fDelegate;
/**
* Decorate <code>aDelegate</code> to make sure if parser everything or nothing
*
* @param aDelegate The delegate format
*/
public ParseAllFormat( Format aDelegate ) {
fDelegate = aDelegate;
}
@Override
public StringBuffer format( Object obj, StringBuffer toAppendTo, FieldPosition pos ) {
return fDelegate.format( obj, toAppendTo, pos );
}
@Override
public AttributedCharacterIterator formatToCharacterIterator( Object obj ) {
return fDelegate.formatToCharacterIterator( obj );
}
@Override
public Object parseObject( String source, ParsePosition pos ) {
int initialIndex = pos.getIndex();
Object result = fDelegate.parseObject( source, pos );
if ( result != null && pos.getIndex() < source.length() ) {
int errorIndex = pos.getIndex();
pos.setIndex( initialIndex );
pos.setErrorIndex( errorIndex );
return null;
}
return result;
}
@Override
public Object parseObject( String source ) throws ParseException {
//no need to delegate the call, super will call the parseObject( source, pos ) method
return super.parseObject( source );
}
}
这两个类别的组合允许以下代码
import java.text.Format;
import java.text.ParseException;
import java.text.SimpleDateFormat;
public class FormattingDemo {
private static Format createCompositeDateFormat(){
Format formattingFormat = new ParseAllFormat( new SimpleDateFormat( "dd.MM.yyyy" ) );
SimpleDateFormat shortFormat = new SimpleDateFormat( "dd.MM.yy" );
Format otherFormat = new ParseAllFormat( shortFormat );
CompositeFormat compositeFormat = new CompositeFormat();
compositeFormat.addFormat( otherFormat );
compositeFormat.addFormat( formattingFormat );
compositeFormat.setFormattingFormat( formattingFormat );
return compositeFormat;
}
public static void main( String[] args ) throws ParseException {
Format dateFormat = createCompositeDateFormat();
System.out.println( dateFormat.parseObject( "27.01.2010" ) );
System.out.println( dateFormat.parseObject( "27.01.10" ) );
System.out.println( dateFormat.parseObject( "27.01.2012" ) );
System.out.println(dateFormat.format( dateFormat.parseObject( "27.01.2010" ) ));
System.out.println(dateFormat.format( dateFormat.parseObject( "27.01.10" ) ));
System.out.println(dateFormat.format( dateFormat.parseObject( "27.01.2012" ) ));
}
}
导致以下输出
Wed Jan 27 00:00:00 CET 2010
Wed Jan 27 00:00:00 CET 2010
Fri Jan 27 00:00:00 CET 2012
27.01.2010
27.01.2010
27.01.2012
请注意,有一个小问题我没有找到一个像样的解决方案。将Format
实例添加到CompositeFormat
的顺序也是对它们进行解析评估的顺序。在这种情况下,您需要按正确的顺序添加它们,因为即使是new SimpleDateFormat( "dd.MM.yyyy" )
似乎也接受输入字符串27.01.10
,并且可以将整个String
解析为等效于27.01.0010
的Date
对象。