Sorting XSL by date in Month-Name Day-Number Year-

2019-02-28 07:04发布

问题:

I have a Joomla SobiPro database of events that each contain a date field named "start_date" that looks like "Oct 10 2015" and "Jan 03 2016". I need to search on a different field (distance from a given zip code in miles) and then output the results sorted by those dates in ascending order.

I'm completely new to XSL/SobiPro/Joomla so I'm struggling but after a lot of googling I have found that I need to insert an XSL sort command at the location indicated below by <!-- **** Insert Sort Command Here **** --> in the SobiPro template file common/entries.xsl:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
        <xsl:output method="xml" doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd" encoding="UTF-8" />
        <xsl:include href="vcard.xsl" />
        <xsl:include href="manage.xsl" />

        <xsl:template name="entriesLoop">
                <xsl:variable name="entriesInLine">
                        <xsl:value-of select="entries_in_line" />
                </xsl:variable>

                <xsl:variable name="cellClass">
                        <xsl:value-of select="floor( 12 div $entriesInLine )" />
                </xsl:variable>

                <xsl:variable name="entriesCount">
                        <xsl:value-of select="count(entries/entry)" />
                </xsl:variable>

                <xsl:comment>entries loop - start</xsl:comment>
        <div class="entry-container">
                <xsl:for-each select="entries/entry">

<!-- **** Insert Sort Command Here **** -->

                    <xsl:if test="($entriesInLine > 1 and (position() = 1 or (position() mod $entriesInLine) = 1 )) or $entriesInLine = 1">
                        <!-- opening the "table" row -->
                        <xsl:text disable-output-escaping="yes">&lt;div class="row-fluid" &gt;</xsl:text>
                    </xsl:if>
                    <div class="span{$cellClass}">
                        <xsl:call-template name="vcard" />
                    </div>
                    <xsl:if test="($entriesInLine > 1 and ((position() mod $entriesInLine) = 0 or position() = $entriesCount)) or $entriesInLine = 1">
                        <!-- closing the "table" row -->
                        <xsl:text disable-output-escaping="yes">&lt;/div&gt;</xsl:text>
                    </xsl:if>
                </xsl:for-each>
        </div>
                <xsl:comment>entries loop - end</xsl:comment>

        </xsl:template>
</xsl:stylesheet>

I am not familiar with XSL at all but I did find that if the date format was 2015 10 09 instead of Oct 09 2015 then I could replace the commented line above with this line:

<xsl:sort select="fields/field_start_date" order="ascending" />

and the sort would succeed since it appears to be sorting alphabetically. I cannot change the date format, nor can I add a second field that is the same date in that sortable format.

I also found that if I left the date format as-is I could get the sort by year and then by month name if I did:

<xsl:sort select="concat(
        substring(fields/field_start_date,string-length(fields/field_start_date)-3,4),
        substring(fields/field_start_date,string-length(fields/field_start_date)-10,3),
        substring(fields/field_start_date,string-length(fields/field_start_date)-6,2)
                         )" order="ascending" />

but that wont produce the desired ascending-dates output order because Feb comes before Jan alphabetically, etc.

I know it looks weird to be counting chars back from the end of the date to find the start of each data component (year, day, and month) but I tried counting forward from both zero and 1 and could not make it work. It seems like the month name starts at character position 9 (idk why - something about start_date being a compound field with a string+timezone+other info I expect) but then the day number doesn't start at position 13 as you'd expect and I gave up trying to find it by trial and error.

Given the above though I still need to figure out how to map the month name abbreviation to the number (Jan->01, Feb->02, etc.) and I've found various examples of ways to do that, e.g. https://stackoverflow.com/a/555536/1745001 by @DimitreNovatchev, but after a day of research and trial-and-error I just can't figure out how to rearrange that or any other similar code to fit into my existing XSL file and work in my case.

Can anyone help me figure this out?


Context:

If you got to my site, http://tournamart.com/, scroll down below the map, and enter these values in the given fields:

Distance From: [Oswego, Illinois, USA]  [(Drop Down:) 100 miles]
Start Date
    From: [10/01/2015]
    To:   [03/01/2015]

and then click on the green "Start Search" button, the site will print a list of the 9 tournaments coming up between Oct 1 2015 and Mar 1 2016 within 100 miles of Oswego. I'm trying to get the output sorted by date so the earliest tournament comes first. With not modification to the code the output, best I can tell, is instead ordered by the order it was entered in the database.

Given the proposed solution below (https://stackoverflow.com/a/32904143/1745001):

<xsl:sort select="substring(fields/field_start_date, 8 , 4)" data-type="number" />
<xsl:sort select="string-length(substring-before('JanFebMarAprMayJunJulAugSepOctNovDec', substring(fields/field_start_date, 1 , 3)))" data-type="number" />
<xsl:sort select="substring(fields/field_start_date, 5 , 2)" data-type="number" />

the output is still not sorted by date:

PUMA 2016 PRESIDENTS DAY INDOOR CUP
City: Sturtevant
State: Wisconsin
Start Date: Feb 13 2016

PUMA 2016 MARTIN LUTHER KING INDOOR CUP
City: Sturtevant
State: Wisconsin
Start Date: Jan 15 2016

Winter Freeze
City: Crown Point
State: Indiana
Start Date: Dec 04 2015

2015 Eclipse Select AT&T Oktoberfest Shootout
City: Waukegan
State: Illinois
Start Date: Oct 10 2015

Racine Lighthouse Classic - 2015
City: Franksville
State: Wisconsin
Start Date: Oct 10 2015

Sockers Nike Classic Cup Fall 2015
City: Chicago
State: Illinois
Start Date: Oct 09 2015

2015 Octoberfest Classic Presented by Quaker Oats
City: Libertyville
State: Illinois
Start Date: Oct 09 2015

WSA Columbus Day Classic
City: Wheeling
State: Illinois
Start Date: Oct 09 2015

Glen Ellyn Lakers FC 2015 Fall Classic
City: Glen Ellyn
State: Illinois
Start Date: Oct 02 2015

but when I modify the code to count characters back from the end of the date string:

<xsl:sort select="substring(fields/field_start_date, string-length(fields/field_start_date)-3 , 4)" data-type="number" />
<xsl:sort select="string-length(substring-before('JanFebMarAprMayJunJulAugSepOctNovDec', substring(fields/field_start_date, string-length(fields/field_start_date)-10 , 3)))" data-type="number" />
<xsl:sort select="substring(fields/field_start_date, string-length(fields/field_start_date)-6 , 2)" data-type="number" />

it seems like it succeeds as it now generates:

Glen Ellyn Lakers FC 2015 Fall Classic
City: Glen Ellyn
State: Illinois
Start Date: Oct 02 2015

Sockers Nike Classic Cup Fall 2015
City: Chicago
State: Illinois
Start Date: Oct 09 2015

2015 Octoberfest Classic Presented by Quaker Oats
City: Libertyville
State: Illinois
Start Date: Oct 09 2015

WSA Columbus Day Classic
City: Wheeling
State: Illinois
Start Date: Oct 09 2015

2015 Eclipse Select AT&T Oktoberfest Shootout
City: Waukegan
State: Illinois
Start Date: Oct 10 2015

Racine Lighthouse Classic - 2015
City: Franksville
State: Wisconsin
Start Date: Oct 10 2015

Winter Freeze
City: Crown Point
State: Indiana
Start Date: Dec 04 2015

PUMA 2016 MARTIN LUTHER KING INDOOR CUP
City: Sturtevant
State: Wisconsin
Start Date: Jan 15 2016

PUMA 2016 PRESIDENTS DAY INDOOR CUP
City: Sturtevant
State: Wisconsin
Start Date: Feb 13 2016

So, that is immensely helpful, thank you, and the remaining questions are:

  1. Why do I have to count char positions back from the end?
  2. Is there a more efficient (or robust or otherwise "better") way to do it?

回答1:

I still need to figure out how to map the month name abbreviation to the number (Jan->01, Feb->02, etc.)

You can do:

string-length(substring-before('JanFebMarAprMayJunJulAugSepOctNovDec', substring($f, 1 , 3)))

This maps the abbreviations as Jan->0, Feb->3, Mar->6, etc. which is quite sufficient for a numerical sort.

Instead of trying to manufacture a single text string, I suggest you simply use three numerical sort instructions:

<xsl:sort select="substring(fields/field_start_date, 8 , 4)" data-type="number" />
<xsl:sort select="string-length(substring-before('JanFebMarAprMayJunJulAugSepOctNovDec', substring(fields/field_start_date, 1 , 3)))" data-type="number" />
<xsl:sort select="substring(fields/field_start_date, 5 , 2)" data-type="number" />

I know it looks weird to be counting chars back from the end of the date to find the start of each data component (year, day, and month) but I tried counting forward from both zero and 1 and could not make it work.

If it still doesn't work for you, post an example of an input where it fails.



标签: xslt