JXDatePicker использует SimpleDateFormat для форматирования dd.MM.yy в dd.MM.yyyy с текущим веком - PullRequest
5 голосов
/ 27 января 2012

Как уже объяснено, я хочу добиться, чтобы, когда пользователь редактировал дату в 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 в качестве формата для отображения дат, а также хочу, чтобы он отображался в режиме редактирования.Например, когда пользователь кликает на дату, например 27.01.2012, я также хочу, чтобы она была такой же в режиме редактирования, а не только в краткой форме: 27.01.12.

Моя проблема сейчас заключается в том, что set2DigitYearStart(Дата), к сожалению, работает только тогда, когда я решу использовать сокращение в режиме редактирования.Я сделал небольшой пример, чтобы показать этот случай (требуется библиотека 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

Ответы [ 3 ]

5 голосов
/ 27 января 2012

Окончательный (надеюсь:)

Сводка первого редактирования:

  • DatePickerFormatter уже реализует стратегию поиска (или CompoundFormat, как предложено @Robin)
  • последовательность поиска для разбора настраивается с помощью клиентского кода
  • идея состоит в том, чтобы попытаться выполнить синтаксический анализ, начиная с первого (обычно «самого длинного»), если это не удается, попробуйте следующий (обычно)не так долго ") и т. д. до тех пор, пока не будет выполнено успешное выполнение или пока не возникнет исключение parseException
  • для анализа года, SimpleDateFormat имеет правила, которые конфликтуют с этим самым длинным первым поиском: для этого требуется, чтобы" yy "пробовалось до" yyyy«
  • выполнение этого в 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);

Редактировать

headпосле прочтения недавнего ответа Робина (+1!) - наконец, после нескольких лет я смущаюсь, что SwingX 'DatePickerFormatter пытается сделать: это поддерживать цепочку форматирования (от длинного к короткому), самый длинныйиспользуется после фиксации, короче, чтобы упростить набор текста пользователями.

К сожалению, это не работает так, как ожидалось.При заданной последовательности форматов, длиннее или короче (и соответственно настроен на столетие):

"yyyy", "yy"

и заданный ввод

"10"

ощущается как передаваемый с первого на второе, в результате чегов

 2010

но это не так.Как задокументировано (кто читает документацию ... ленивый, кашляю ...) в SimpleDateFormat

Год: [...] Для анализа, если количество букв шаблона больше 2,Год интерпретируется буквально, независимо от количества цифр.Таким образом, используя шаблон «MM / dd / yyyy», «01/11/12» анализирует до 11 января 12 года нашей эры.

В конце дня - поскольку DatePickerFormatter пытается поддержать этот поиск, ноне удалась - в конце концов это может считаться проблемой SwingX: -)

2 голосов
/ 27 января 2012

Я не совсем осведомлен о 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".

Надеюсь, это поможет или поможет применить это к вашему сценарию.

1 голос
/ 25 августа 2012

Клеопатра уже объяснила, как установить Format на сборщике даты. Для этого варианта использования я бы применил комбинацию CompositeFormat и ParseAllFormat вместо отдельного формата для редактирования и обычного режима, чтобы избежать изменения 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 );
  }
}

ParseAllFormat

Как правило, для пользовательского ввода вы хотите, чтобы весь пользовательский ввод мог быть отформатирован / проанализирован, чтобы пользователь не мог вводить строку, которая является полукорректной. ParseAllFormat - это декоратор для обычного Format, который выдает ParseException с, когда может быть проанализирована только часть String.

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 в Date объект, эквивалентный 27.01.0010.

...