5

正如我已经解释的那样,当用户在 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 作为格式来显示日期,并且还希望它像在编辑模式中那样显示。例如,当用户单击像 27.01.2012 这样的日期时,我也希望它在编辑模式下也一样,而不仅仅是缩写形式:27.01.12。

我现在的问题是,不幸的是 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 );
  }
}

任何人都已经有相同的要求,可以告诉我如何使这项工作?欢迎任何想法。非常感谢您提前。梅内

4

3 回答 3

5

决赛(希望:)

第一次编辑总结:

  • 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 均不支持两种格式:看看,尝试使用核心 JFormattedTextField 实现您的目标 :-)

出路可能是 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 问题:-)

于 2012-01-27T16:49:14.470 回答
2

我不太了解 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”模式返回它的格式。

希望这有助于或有助于将其应用于您的场景。

于 2012-01-27T14:59:27.643 回答
1

kleopatra 已经解释了如何Format在日期选择器上设置 a。对于这个用例,我将应用 aCompositeFormatParseAllFormat而不是单独的编辑格式和常规模式的组合,以避免String在开始编辑时更改 (正如您已经注意到的)。

复合格式

复合格式,顾名思义,是类的复合实现Format但仅用于解析。对于格式,它使用一个Format. 这允许用户以多种形式输入他/她的日期,同时通过使用一种特定的格式来统一格式化。

您也可以通过编写更复杂的Format. SimpleDateFormat但在这种情况下,只使用JDK 类提供的格式化/解析功能会更容易。

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 );
  }
}

解析所有格式

通常对于用户输入,您希望可以对整个用户输入进行格式化/解析,以避免用户可以输入一半正确的字符串。TheParseAllFormat是一个正则的装饰器,当只有一部分可以被解析时Format抛出s 。ParseExceptionString

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等效Date27.01.0010.

于 2012-08-25T19:24:29.253 回答