Преобразувайте всяка дата (отвсякъде) в ДД-ММ-ГГГГ

Опитвам се да създам функция, която може да получи дата в различни формати и (езици) и да я трансформира в DD-MM-YYYY. Например тази функция може да получи 22 Fev 2011 (португалски), а също и 22 Feb 2011 (английски). И за двете трябва да върне 22-02-2011.

Да приемем, че имам ограничено количество езици, така че мога да имам някаква структура от данни, която носи месеците и ги съкращава. Въпросът ми е: "Ако приемем, че strtotime работи за низове на английски, какъв е най-добрият ми избор за създаване на функция, която дава низ от дата на език X, връща дата с формат DD-MM-YYY?"


person Nobita    schedule 23.09.2011    source източник
comment

Имам следното наследяване на обект, нека започнем с корена (това е просто таблица с автоматично нарастващо цяло число):

@Entity
@Table(name = "Contacts")
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn(name = "discriminator", discriminatorType = DiscriminatorType.STRING)
public abstract class Contact implements Serializable
{
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column
    protected Integer id;

    ...
}

OrganizationalUnit е само име плюс наследен ID:

@Entity
@Table(name = "OrganizationalUnits")
public abstract class OrganizationalUnit extends Contact
{
    @Column
    protected String name;

    ...
}

Organization е просто празен обект (за справка):

@Entity
@Table(name = "Organizations")
public abstract class Organization extends OrganizationalUnit
{
    ...
}

Не на последно място:

@Entity
@Table(name = "Companies")
@DiscriminatorValue(value = "company")
public class Company extends Organization
{
    @Basic(optional = false)
    @Column(name = "dnd_type")
    private String dndType;

    ...
}

Това кара компанията да има 3 колони/полета: ID, име и тип dnd.

Всяка компания се позовава от обект на документ в системата (всеки документ има компания собственик):

@Entity
@Table(name = "Documents")
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn(name = "discriminator", discriminatorType = DiscriminatorType.STRING)
public abstract class Document implements Serializable
{
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column
    protected Integer id;

    @Basic(optional = false)
    @Column(name = "file_name")
    protected String fileName;

    @ManyToOne(fetch = FetchType.LAZY, optional = false)
    @JoinColumn(name = "company_id", referencedColumnName = "id")
    protected Company company;

    ...
}

Обърнете внимание на @ManyToOne(fetch = FetchType.LAZY, optional = false) във връзката с компанията. Задължително е да нямате LAZY тук, използването на EAGER тип извличане тук ПОКРЕБЯВА проблема, за който говоря тук (но искам да го накарам да работи с LAZY, също и защото използвам генератор на код за повторно генериране на класовете обекти... и трябва да работи!)

Позовавам се на dndType на Company за всеки документ в JSF страница pq-edit.xhtml като:

<ui:include src="/subviews/repo-filename-dnd-panel.xhtml">
  <ui:param name="document" value="#{doc}" />
  <ui:param name="documentFileName" value="#{doc.fileName}" />
  <ui:param name="documentCompanyName" value="#{doc.company.name}" />
  <ui:param name="documentCompanyDndSuffix" value="#{doc.company.dndType}" /> <!-- EXCEPTION HERE -->
</ui:include>

Те просто се предават на подизглед /subviews/repo-filename-dnd-panel.xhtml, където просто препращам към параметрите, напр. documentCompanyDndSuffix, за да създадете някакъв HTML.

Това, което получавам за #{doc.company.dndType} сега е:

13:53:56,043 SEVERE [org.richfaces.log.Context] (http--127.0.0.1-8080-3) /subviews/repo-filename-dnd-panel.xhtml @14,63 type="doc-#{documentCompanyDndSuffix}": /pq-edit.xhtml @139,94 value="#{doc.company.dndType}": org.hibernate.LazyInitializationException: could not initialize proxy - no Session: javax.el.ELException: /subviews/repo-filename-dnd-panel.xhtml @14,63 type="doc-#{documentCompanyDndSuffix}": /pq-edit.xhtml @139,94 value="#{doc.company.dndType}": org.hibernate.LazyInitializationException: could not initialize proxy - no Session
    at com.sun.faces.facelets.el.TagValueExpression.getValue(TagValueExpression.java:114) [jsf-impl-2.1.5-jbossorg-1.jar:2.1.5-SNAPSHOT]
    at javax.faces.component.ComponentStateHelper.eval(ComponentStateHelper.java:194) [jboss-jsf-api_2.1_spec-2.0.0.Beta1.jar:2.0.0.Beta1]
    at javax.faces.component.ComponentStateHelper.eval(ComponentStateHelper.java:182) [jboss-jsf-api_2.1_spec-2.0.0.Beta1.jar:2.0.0.Beta1]
    at org.richfaces.component.UIDragSource.getType(UIDragSource.java:95) [richfaces-components-ui-4.1.0.Final.jar:]
    at org.richfaces.renderkit.DragSourceRenderer.getOptions(DragSourceRenderer.java:55) [richfaces-components-ui-4.1.0.Final.jar:]
    at org.richfaces.renderkit.DnDRenderBase.buildClientScript(DnDRenderBase.java:66) [richfaces-components-ui-4.1.0.Final.jar:]
    at org.richfaces.renderkit.DnDRenderBase.buildAndStoreScript(DnDRenderBase.java:43) [richfaces-components-ui-4.1.0.Final.jar:]
    at org.richfaces.renderkit.DnDRenderBase.doEncodeEnd(DnDRenderBase.java:74) [richfaces-components-ui-4.1.0.Final.jar:]
    at org.richfaces.renderkit.RendererBase.encodeEnd(RendererBase.java:175) [richfaces-components-ui-4.1.0.Final.jar:]
    at javax.faces.component.UIComponentBase.encodeEnd(UIComponentBase.java:875) [jboss-jsf-api_2.1_spec-2.0.0.Beta1.jar:2.0.0.Beta1]
    at javax.faces.component.UIComponent.encodeAll(UIComponent.java:1763) [jboss-jsf-api_2.1_spec-2.0.0.Beta1.jar:2.0.0.Beta1]
    at org.richfaces.renderkit.RendererBase.renderChildren(RendererBase.java:276) [richfaces-components-ui-4.1.0.Final.jar:]
    at org.richfaces.renderkit.html.AjaxOutputPanelRenderer.encodeChildren(AjaxOutputPanelRenderer.java:57) [richfaces-components-ui-4.1.0.Final.jar:]
.
. lots of RichFaces and JSF stuff
.
    at org.apache.coyote.http11.Http11AprProtocol$Http11ConnectionHandler.process(Http11AprProtocol.java:626) [jbossweb-7.0.7.Final.jar:]
    at org.apache.tomcat.util.net.AprEndpoint$Worker.run(AprEndpoint.java:2033) [jbossweb-7.0.7.Final.jar:]
    at java.lang.Thread.run(Unknown Source) [:1.7.0_02]
Caused by: org.hibernate.LazyInitializationException: could not initialize proxy - no Session
    at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:149) [hibernate-core-4.0.0.Final.jar:4.0.0.Final]
    at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:195) [hibernate-core-4.0.0.Final.jar:4.0.0.Final]
    at org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer.invoke(JavassistLazyInitializer.java:185) [hibernate-core-4.0.0.Final.jar:4.0.0.Final]
    at de.poyry.pqgenerator.model.Company_$$_javassist_22.getDndType(Company_$$_javassist_22.java) [classes:]
    at sun.reflect.GeneratedMethodAccessor457.invoke(Unknown Source) [:1.7.0_02]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) [:1.7.0_02]
    at java.lang.reflect.Method.invoke(Unknown Source) [:1.7.0_02]
    at javax.el.BeanELResolver.getValue(BeanELResolver.java:302) [jboss-el-api_2.2_spec-1.0.0.Final.jar:1.0.0.Final]
    at com.sun.faces.el.DemuxCompositeELResolver._getValue(DemuxCompositeELResolver.java:176) [jsf-impl-2.1.5-jbossorg-1.jar:2.1.5-SNAPSHOT]
    at com.sun.faces.el.DemuxCompositeELResolver.getValue(DemuxCompositeELResolver.java:203) [jsf-impl-2.1.5-jbossorg-1.jar:2.1.5-SNAPSHOT]
    at org.apache.el.parser.AstValue.getValue(AstValue.java:169) [jbossweb-7.0.7.Final.jar:]
    at org.apache.el.ValueExpressionImpl.getValue(ValueExpressionImpl.java:189) [jbossweb-7.0.7.Final.jar:]
    at org.jboss.weld.el.WeldValueExpression.getValue(WeldValueExpression.java:50) [weld-core-1.1.4.Final.jar:2011-11-22 20:01]
    at com.sun.faces.facelets.el.TagValueExpression.getValue(TagValueExpression.java:109) [jsf-impl-2.1.5-jbossorg-1.jar:2.1.5-SNAPSHOT]
    at org.apache.el.parser.AstIdentifier.getValue(AstIdentifier.java:67) [jbossweb-7.0.7.Final.jar:]
    at org.apache.el.parser.AstDeferredExpression.getValue(AstDeferredExpression.java:44) [jbossweb-7.0.7.Final.jar:]
    at org.apache.el.parser.AstCompositeExpression.getValue(AstCompositeExpression.java:50) [jbossweb-7.0.7.Final.jar:]
    at org.apache.el.ValueExpressionImpl.getValue(ValueExpressionImpl.java:189) [jbossweb-7.0.7.Final.jar:]
    at org.jboss.weld.el.WeldValueExpression.getValue(WeldValueExpression.java:50) [weld-core-1.1.4.Final.jar:2011-11-22 20:01]
    at com.sun.faces.facelets.el.TagValueExpression.getValue(TagValueExpression.java:109) [jsf-impl-2.1.5-jbossorg-1.jar:2.1.5-SNAPSHOT]
    ... 72 more

Нямам представа защо получавам LazyInitializationException тук, като се има предвид, че обектът Company не подсказва LAZY на Hibernate 4 и предишното извикване на #{doc.company.name} работи добре.

Актуализация:

Наскоро тествах това на GlassFish 3.1.2 и всичко е наред. Така че започвам да вярвам, че това е някакъв JBoss AS 7 или по-скоро JBoss AS проблем с управлението на транзакциите/несъответствие в политиката.

Моето предположение е, че името на компанията е било достъпно и непроксирано по време на някаква транзакция и след като мениджърът на обекти е бил затворен, се осъществява достъп до dndType, което все още не е отменено (докато name е)... изглежда ми като EJB транзакция проблем с боравене (където наистина все още имам дефицит)...

Ще добавя повече информация тук скоро.

  -  person Ilia Choly    schedule 24.09.2011


Отговори (2)


Манипулирането на дата/час със сигурност е болка. :)

Предложено решение

1. Локални данни

Току-що посетих SVN хранилището на Yii и безсрамно копирах тези:

$locales = array(
    'pt' => array(
        'monthNames' => array(
            'wide' => array (
              1 => 'janeiro',
              2 => 'fevereiro',
              3 => 'marco',
              4 => 'abril',
              5 => 'maio',
              6 => 'junho',
              7 => 'julho',
              8 => 'agosto',
              9 => 'setembro',
              10 => 'outubro',
              11 => 'novembro',
              12 => 'dezembro',
            ),
            'abbreviated' => array(
              1 => 'jan',
              2 => 'fev',
              3 => 'mar',
              4 => 'abr',
              5 => 'mai',
              6 => 'jun',
              7 => 'jul',
              8 => 'ago',
              9 => 'set',
              10 => 'out',
              11 => 'nov',
              12 => 'dez',
            ),
        ),
        'weekDayNames' => array(
            'wide' => array (
              0 => 'domingo',
              1 => 'segunda-feira',
              2 => 'terca-feira',
              3 => 'quarta-feira',
              4 => 'quinta-feira',
              5 => 'sexta-feira',
              6 => 'sabado',
            ),
            'abbreviated' => array(
              0 => 'dom',
              1 => 'seg',
              2 => 'ter',
              3 => 'qua',
              4 => 'qui',
              5 => 'sex',
              6 => 'sab',
            ),
        ),
     ),
    'en' => array(
        'monthNames' => array(
            'wide' => array (
              1 => 'January',
              2 => 'February',
              3 => 'March',
              4 => 'April',
              5 => 'May',
              6 => 'June',
              7 => 'July',
              8 => 'August',
              9 => 'September',
              10 => 'October',
              11 => 'November',
              12 => 'December',
            ),
            'abbreviated' => array(
              1 => 'Jan',
              2 => 'Feb',
              3 => 'Mar',
              4 => 'Apr',
              5 => 'May',
              6 => 'Jun',
              7 => 'Jul',
              8 => 'Aug',
              9 => 'Sep',
              10 => 'Oct',
              11 => 'Nov',
              12 => 'Dec',
            ),
        ),
        'weekDayNames' => array(
            'wide' => array (
              0 => 'Sunday',
              1 => 'Monday',
              2 => 'Tuesday',
              3 => 'Wednesday',
              4 => 'Thursday',
              5 => 'Friday',
              6 => 'Saturday',
            ),
            'abbreviated' => array(
              0 => 'Sun',
              1 => 'Mon',
              2 => 'Tue',
              3 => 'Wed',
              4 => 'Thu',
              5 => 'Fri',
              6 => 'Sat',
            ),
        ),
    ),
);

2. Грубо налагане на проблема

Ако приемем, че приложението ви не прекарва цялото си време в конвертиране на дати, които могат да се четат от човека, скоростта не би трябвало да има значение. Затова избрах кратко решение с добра разширяемост, с цената на това да не се опитвам да оптимизирам и да съм малко загадъчен.

function strtotimeIntl($timeString, $locales, $normalizeCallback = 'strtolower') {
    // STEP 1 -- TRY ENGLISH
    $ts = strtotime($timeString);
    if ($ts !== false) {
        return $ts;
    }

    // STEP 2 -- BRUTE FORCE
    $english = $locales['en'];

    foreach($locales as $code => $localeInfo) {
        if($code == 'en') {
            continue; // don't try english again
        }

        $subject = $normalizeCallback($timeString); // reset

        // These reflect the structure of $localeInfo
        $replacementKeys = array(
            array('monthNames', 'wide'),
            array('monthNames', 'abbreviated'),
            array('weekDayNames', 'wide'),
            array('weekDayNames', 'abbreviated'),
        );

        // Replace everything present in the string with english equivalents
        foreach($replacementKeys as $keys) {
            $map = array_map($normalizeCallback, $localeInfo[$keys[0]][$keys[1]]);
            $flipped = array_flip($map);
            $subject = preg_replace('/\b('.implode('|', $map).')\b/e',
                                   '$english[$keys[0]][$keys[1]][$flipped[\'$1\']]',
                                   $subject);
        }

        // Does this look right?
        $ts = strtotime($subject);
        if ($ts !== false) {
            return $ts;
        }
    }

    // Give up, it's not like we didn't try
    return false;
}

Това вътрешно foreach наистина изглежда миризливо, но мисля, че е приемливо. Това, което прави, е да се опита да замени всеки подниз, който изглежда като един от елементите в подмасива на $localeInfo (текущият локал, който се тества), идентифициран от индексите $keys[0] и $keys[1]. За да направи заместването възможно най-кратко, той използва спомагателен масив $flipped и preg_replace в режим на оценка; ако не харесвате този вид код, той със сигурност може да бъде заменен с по-познат подход, базиран на цикъл.

3. Как да го използвама

$timeString = '22 Feb 2011';
echo strtotimeIntl($timeString, $locales);

$timeString = '22 Fev 2011';
echo strtotimeIntl($timeString, $locales);

4. Какво става с третия аргумент?

Е, би било хубаво, ако замяната работи по начин, който не е чувствителен към главни и малки букви. Проблемът с това е, че не можете сляпо да използвате strtolower и модификатора на regex /i, защото поне първият няма да работи, освен ако не промените локала LC_TEXT, което е болезнено изискване и не е надеждно за зареждане (имената на локалите зависят от операционната система ). И аргументът с пушка е, че дори и всичко да върви добре дотук, все още трябва да запазите вашите данни за локал в ANSI кодиране (което означава, че не можете да ги запазите всички в един и същ файл).

Следователно повикващият има възможност да предостави своя собствена функция за нормализиране, ако е необходимо; mb_strtolower би бил отличен избор тук, ако данните ви се записват в UTF-8.

5. Това изобщо работи ли?

Разбира се, че е така.

6. И няма никакви уговорки?

Е, освен функцията за нормализиране, има още една, за която се сещам: strtotime вътрешно използва местната часова зона, за да преобразува анализираната дата в клеймо за време. Това означава, че дата в напр. Френският ще бъде анализиран правилно при съответните данни за локализация, но клеймото за време ще бъде произведено за местната часова зона, а не за CET/CEST (часовата зона, която Франция използва). В зависимост от вашите изисквания може също да искате да отчетете разликата в часовата зона.

person Jon    schedule 23.09.2011
comment
@genesisφ: Благодаря! Трябваше да напиша код за манипулиране на часова зона и дата/час за Moodle през 2004 г., когато PHP нямаше такъв; оттогава това е лично между мен и дата/часове. ;) - person Jon; 24.09.2011
comment
@Jon: Ето защо обичам SO. Вие не само ми дадохте идея, но и вложихте усилия да напиша този код. Благодаря ви много, това беше наистина полезно. - person Nobita; 24.09.2011

Освен да използвате (т.е. да плащате за) услуги на API за превод, можете да създадете таблици на база данни за languages, weekdays, abbreviated weekdays, months, abbreviated months. Четирите таблици ден/месец ще имат външен ключ language_id. Можете да съхранявате английските еквиваленти в редовете или, по-добре, да ги нормализирате.

След това накарайте функцията да вземе алфа токените от низа за дата (preg_match) и потърсете в таблиците редове, които съответстват на токена и езика. След това, ако се върнат подходящите редове, заместете английските токени в низа за дата и преминете към дата функция.

person webbiedave    schedule 23.09.2011