JXDatePicker 使用 SimpleDateFormat 将 dd.MM.yy 格式化为当前世纪的 dd.MM.yyyy

发布于 2024-12-29 08:27:22 字数 2334 浏览 2 评论 0原文

正如已经解释过的,我想要实现的目标是,当用户在 JXDatePicker 中编辑日期时,他可以选择是否以相同的格式再次键入日期,默认情况下是 dd.MM.yyyy 或只是 dd.MM.yy 。当他使用缩写形式时,我希望选择器选择当前世纪。

示例:

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 日这样的日期时,我也希望它在编辑模式下也如此,而不仅仅是缩写形式: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 );
  }
}

有人已经有相同的要求并且可以告诉我如何使这项工作有效吗?欢迎任何想法。预先非常感谢您。伊梅内

as already explained I want to achieve, that when the user is editing a date within a JXDatePicker, he can choose, weather he types it again in the same format, which is by default dd.MM.yyyy or just dd.MM.yy. When he uses the short form I want the Picker to choose the current century.

Example:

27.01.2012 edited to 27.01.10 should result in 27.01.2010

as well as:

27.01.2012 edited to 27.01.2010 should also result in 27.01.2010

By default the JXDatePicker handels it the following way:

27.01.2012 edited to 27.01.10 results in 27.01.0010

Which is not really the way I wanted it to work. After some short research I found the following Method in 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)

On first view this sounded exactly like what I need. So I tested it and unfortunatly it didnt work like I hoped it would. This is because I want to use dd.MM.yyyy as format to display dates and also want it to be displayed like that in editmode. For example when the user klicks on a date like 27.01.2012, I also want it to be like that in editmode, too and not just the short form: 27.01.12.

My Problem now is, that set2DigitYearStart(Date) unfortunatly only works, when I choose to use the shortform in editmode. I made a small example to show this case (SwingX Library is required, because of jxdatepicker and can be found be here).

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

Anyone already had the same requirement and can tell me how to make this work? Any ideas are welcome. Thank you very much in advance. ymene

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

人事已非 2025-01-05 08:27:22

最终(希望:)

第一次编辑摘要:

  • DatePickerFormatter已经实现了查找策略(或CompoundFormat,如@Robin建议)
  • 用于解析的查找序列可以由客户端代码配置,
  • 想法是尝试解析从第一个(通常是“最长的”)开始,如果失败尝试下一个(通常是“不太长”),依此类推,直到成功或抛出 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 已经指出的那样)。没有一个默认格式同时支持两种格式:要看看,尝试使用核心 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”解析为 Jan 11, 12 AD

结束时 - 由于 DatePickerFormatter 尝试支持该查找但没有成功 - 这可能会被考虑毕竟是 SwingX 问题:-)

Final (hopefully :)

Summary of the first edit:

  • DatePickerFormatter already implements a lookup strategy (or CompoundFormat, as suggested by @Robin)
  • the lookup sequence for parsing is configurable by client code
  • the idea is to try parsing starting with the first (typically the "longest"), if that fails try the next (typically "not-so-long") and so on until succeeded or a parseException is thrown
  • for year parsing, SimpleDateFormat has rules that conflict with that longest-first lookup: it requires that "yy" is tried before "yyyy"
  • doing so in datePicker has the unwanted side-effect of always showing the date with the short year format

The reason is DatePickerFormatter: it doesn't allow to specify the formatting format (simply uses the first). The way out is a custom DatePickerFormatter, which supports it (in the snippet, it's hardcoded to use the second):

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

Not entirely sure if we should support configuring the formatter in the base class. The DatePickerFormatter is a bit strange beast, not extending InternalFormatter and with the lookup process being a bit in competition with a FormatterFactory...

Original

It's not exactly the datePicker which handles it that way, it's the core formatting (as D1e already noted). None of the default format/ter/s support two formats at the same time: to see, try to achieve your goal with a core JFormattedTextField :-)

The way out might be a FormatterFactory: it allows to use different formats, depending on context: display and edit - the latter is used when the field is focused, the former at all other times. As the picker's editor is a JFormattedTextField, you can configure it directly (instead of using the setFormats methods)

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

Edit

head banging after reading Robin's recent answer (+1!) - at last, embarassingly after years and years, I understand what SwingX' DatePickerFormatter is trying to do: that is to support a lookup chain of formatters (from longer to shorter), the longest used after committing, the shorter to ease the typing by users.

Unfortunately that doesn't work as intuitively expected. Given a sequence of formats, longer to shorter (and appropriately configured to the century):

"yyyy", "yy"

and given input

"10"

feels like being passed on from first to second, resulting in

 2010

but isn't. As documented (who reads documention ... lazy me, cough ...) in SimpleDateFormat

Year: [ ... ] For parsing, if the number of pattern letters is more than 2, the year is interpreted literally, regardless of the number of digits. So using the pattern "MM/dd/yyyy", "01/11/12" parses to Jan 11, 12 A.D.

At the end of the day - as DatePickerFormatter tries to support that lookup but isn't successful - this might be considered a SwingX problem, after all :-)

乖乖公主 2025-01-05 08:27:22

我不太清楚 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”格式返回它 图案。

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

I am not quite aware of JXDatePicker specifically, but if the concrete functionality you want to simulate is: Both user inputs 27.01.2010 and 27.01.10 independently should result in 27.01.2010

Then this will work:

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

The thing is that first you parse input with "dd.MM.yy" pattern and then return it formatting with "dd.MM.yyyy" pattern.

Hope this helps or helps applying this to your scenario.

長街聽風 2025-01-05 08:27:22

kleopatra 已经解释了如何在日期选择器上设置格式。对于此用例,我将应用 CompositeFormatParseAllFormat 的组合,而不是使用单独的格式进行编辑和常规模式,以避免更改 String< /code> 当您开始编辑时(正如您已经注意到的那样)。

复合格式

复合格式,顾名思义,是一种复合实现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

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.0010Date 对象。

kleopatra already explained on how to set a Format on the date picker. For this use-case, I would apply a combination of a CompositeFormat and ParseAllFormat instead of having a separate format for editing and regular mode to avoid changing the String when you start editing (as you already noticed).

Composite format

The composite format, as the name suggests, is a composite implementation of the Format class but only for the parsing. For the formatting, it uses one Format. This allows the user to input his/her date in many forms, while it is formatted consistently by using one specific format to format.

You can obtain this behavior as well by writing one more sophisticated Format. But in this case, it is easier to just use the formatting/parsing functionality offered by the SimpleDateFormat class of the 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 );
  }
}

ParseAllFormat

Typically for user input you want that the whole user input can be formatted/parsed to avoid that the user can input a String which is half-correct. The ParseAllFormat is a decorator for a regular Format which throws ParseExceptions when only part of the String can be parsed.

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

The combination of these both classes allows for the following code

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

resulting in the following output

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

Note that there is a small catch for which I did not found a decent solution. The order in which you add Format instances to the CompositeFormat is also the order in which they are evaluated for the parsing. In this case you need to add them in the correct order as even the new SimpleDateFormat( "dd.MM.yyyy" ) seems to accept the input string 27.01.10 and can parse the whole String to a Date object equivalent to 27.01.0010.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文