Extract YearWeek from a date - xquery

I am trying to achieve a Java code in Xqery for converting a data string into a yearweek combination in most efficient way.
I am getting below below
<ValidFromDttm>2017-02-13T00:00:00.001+00:00</ValidFromDttm>
and output should be --> 201707
Another example --
<ValidToDttm>2019-12-08T23:59:59.001+00:00</ValidToDttm>
output should be -->201949
Could someone suggest me if there is any such option available in Xquery

format-dateTime has a [W] specifier for the week so if you have access to a version respectively processor of XQuery supporting that you can use e.g. format-dateTime(xs:dateTime(.), '[Y0001][W01]'), a more complete example is
root/date/format-dateTime(xs:dateTime(.), '[Y0001][W01]')
which for the input document
<root>
<date>2017-02-13T00:00:00.001+00:00</date>
<date>2019-12-08T23:59:59.001+00:00</date>
</root>
outputs 201707 201949
https://xqueryfiddle.liberty-development.net/b4GWVd

This old post from Jakob Fix might help you: http://x-query.com/pipermail/talk/2010-November/003298.html
xquery version "1.0";
declare namespace exslt = "http://exslt.org/dates-and-times";
(: for those implementations without exslt extensions :)
declare namespace date = "http://noexslt.org/dates-and-times";
declare variable $date-time := "2010-01-02T00:00:00Z";
declare variable $month-lengths := (0, 31, 28, 31, 30, 31, 30, 31, 31,
30, 31, 30, 31);
(:
: returns the week of the year as a number.
:)
declare function date:week-in-year( $date-time as xs:dateTime ) as xs:integer
{
let $year := fn:year-from-dateTime( $date-time )
let $day := fn:day-from-dateTime( $date-time )
let $month := fn:month-from-dateTime( $date-time )
let $days := sum( subsequence( $month-lengths, 1, $month ) )
let $is-leap := ($year mod 4 = 0 and $year mod 100 != 0) or $year
mod 400 = 0
return date:_week-in-year($year, $days + $day + (if ($is-leap and
$month > 2) then 1 else 0))
};
declare function date:_week-in-year( $year as xs:integer, $month-days
as xs:integer) as xs:integer
{
let $previous-year := $year - 1
let $is-leap := ($year mod 4 = 0 and $year mod 100 != 0) or $year
mod 400 = 0
let $dow := ($previous-year + floor($previous-year div 4) -
floor($previous-year div 100) + floor($previous-year div 400) +
$month-days) mod 7
let $day-of-week := if ($dow > 0) then $dow else 7
let $start-day := ($month-days - $day-of-week + 7) mod 7
let $week-number := floor(($month-days - $day-of-week + 7) div 7)
cast as xs:integer
return
if ($start-day >= 4) then $week-number + 1
else if ($week-number = 0) then
let $leap-day := if ((not($previous-year mod 4) and
$previous-year mod 100) or not($previous-year mod 400)) then 1 else 0
return date:_week-in-year( $previous-year, 365 + $leap-day )
else $week-number
};
exslt:week-in-year( $date-time ),
date:week-in-year( $date-time cast as xs:dateTime )

Related

MarkLogic Xquery: How to sort string values in a for loop

I've got a sequence that needs to sort a list based off earliest year vs. latest year. Due to some unique values in the year element, it is making the sort a little more complicated. Is there any way to achieve the following?
let $dates := ('1982', '2019', '2095', 'pre-1982', 'post-2095')
return
for $date in $dates
order by $date
return $date
the dates element text is usually the year in the data, but outlier cases have a pre- or post- attached. Any way to achieve this minimally?
I am not sure if this is minimal, but it works:
let $dates := ('1982', '2019', '2095', 'pre-1982', 'post-2095')
return
for $date in $dates
let $year :=
if (fn:contains($date, "-"))
then fn:substring-after($date, "-")
else $date
let $prepost :=
if (fn:starts-with($date, "pre"))
then -1
else if (fn:starts-with($date, "post"))
then 1
else 0
order by $year, $prepost
return $date
Just FYI: Definitely not minimal, but I wanted to know what fn:sort does when a sequence is returned. Turns out it does the right thing.
xquery version "3.1";
declare variable $local:ascending := 1;
declare variable $local:descending := -1;
declare function local:sort-prefixed-years ($y, $order) {
if (fn:contains($y, "-"))
then (
let $p := fn:tokenize($y, "-")
let $m :=
switch($p[1])
case "pre" return -1 * $order
case "post" return 1 * $order
default return 0
return (xs:integer($p[2]) * $order, $m)
)
else (xs:integer($y) * $order, 0)
};
declare function local:sort-prefixed-years-ascending ($prefixed-year) {
local:sort-prefixed-years($prefixed-year, $local:ascending)
};
declare function local:sort-prefixed-years-descending ($prefixed-year) {
local:sort-prefixed-years($prefixed-year, $local:descending)
};
let $dates := ('1982', '2019', '2095', 'pre-1982', 'post-2095')
return sort($dates, (), local:sort-prefixed-years-descending#1)

Trouble with making xquery function recursive

I'm having some difficulty with making a function I've written recursive. I need to be able to turn this xml:
<entry ref="22">
<headword>abaishen</headword>
<part_of_speech> v. </part_of_speech>
<variant>abeishen</variant>
<variant>abaissen</variant>
<variant>abeisen</variant>
<variant>abashen</variant>
<variant>abasshen</variant>
<variant>abassen</variant>
<variant>abeeshen</variant>
<variant>abesen</variant>
<variant>abessen</variant>
<variant>abaished</variant>
<variant>-et</variant>
<variant>-it</variant>
<variant>abaisht</variant>
<variant>abaist</variant>
<variant>abasht</variant>
<variant>abast</variant>
</entry>
Into this XML -- essentially replacing the ending of any entry that begins with an "-" with the stem of the last complete entry:
<entry ref="22">
<headword>abaishen</headword>
<variant>abeishen</variant>
<variant>abaissen</variant>
<variant>abeisen</variant>
<variant>abashen</variant>
<variant>abasshen</variant>
<variant>abassen</variant>
<variant>abeeshen</variant>
<variant>abesen</variant>
<variant>abessen</variant>
<variant>abaished</variant>
<variant>abaishet</variant>
<variant>abaishit</variant>
<variant>abaisht</variant>
<variant>abaist</variant>
<variant>abasht</variant>
<variant>abast</variant>
<part_of_speech> v. </part_of_speech>
</entry>
The issue I'm running into is that second entry, the -it one, returns "abaishet" with the code I currently have:
declare function local:hyphen-replace($f) {
let $j :=
if (substring($f/text(), 1, 1) = "-") then
let $ending := substring-after($f/text(),"-")
let $ending-length := string-length($ending)
let $previous := $f/preceding-sibling::*[1]
let $previous-length := string-length($previous)
return
if (substring($previous/text(), 1, 1) = "-") then
local:hyphen-replace($previous)
else
element {name($f)} {concat(substring($previous,1,($previous-length - $ending-length)),$ending)}
else
$f
return $j
};
declare function local:verbCheck($nodes as node()*) as node()* {
let $d := $nodes/part_of_speech
let $s := functx:siblings($d)
let $p := for $node in $nodes
return
let $d := $node/part_of_speech
let $s := functx:siblings($d)
return
if ($d/text() = " v. ") then
for $f in $s
let $j :=
local:hyphen-replace($f)
return ($j)
else
<empty/>
return
($p,$d)
};
<list>
{
let $collection := concat($collection, '?select=*.xml')
let $q := collection($collection)
let $v := local:buildNodes($q)
let $entries :=
for $n in $v
return <entry ref="{$n/#ref}">{local:verbCheck($n)}</entry>
return local:remove-empty-elements($entries)
}
</list>
It's obvious to me that my problem is with this piece of code in local:hypen-replace:
if (substring($previous/text(), 1, 1) = "-") then
local:hyphen-replace($previous)
because it's calling to the immediately previous item and replacing the "-it" node with it's information. But I don't know how to rewrite it to make it work recursively properly. Any suggestions would be appreciated. Thank you.

How can I format a decimal in xquery?

I'm trying to format decimals in XQuery. The decimals are currency, so the format should be ,###.##.
For example:
5573652.23 should be 5,573,652.23
and
352769 should be 352,769 (or 352,769.00 if it's easier/cleaner)
Right now I'm using this function from http://www.xqueryhacker.com/2009/09/format-number-in-xquery/, but I can't use decimals with it:
declare function local:format-int($i as xs:int) as xs:string
{
let $input :=
if ($i lt 0) then fn:substring(fn:string($i), 2)
else fn:string($i)
let $rev := fn:reverse(fn:string-to-codepoints(fn:string($input)))
let $comma := fn:string-to-codepoints(',')
let $chars :=
for $c at $i in $rev
return (
$c,
if ($i mod 3 eq 0 and fn:not($i eq count($rev)))
then $comma else ()
)
return fn:concat(
if ($i lt 0) then '-' else (),
fn:codepoints-to-string(fn:reverse($chars))
)
};
I'm using Saxon 9HE for my processor.
Any help would be greatly appreciated.
----- UPDATE -----
Based on Dimitre's answer, I modified the function to save the decimal portion and add it to the end of the return string.
New Function
declare function local:format-dec($i as xs:decimal) as xs:string
{
let $input := tokenize(string(abs($i)),'\.')[1]
let $dec := substring(tokenize(string($i),'\.')[2],1,2)
let $rev := reverse(string-to-codepoints(string($input)))
let $comma := string-to-codepoints(',')
let $chars :=
for $c at $i in $rev
return (
$c,
if ($i mod 3 eq 0 and not($i eq count($rev)))
then $comma else ()
)
return concat(if ($i lt 0) then '-' else (),
codepoints-to-string(reverse($chars)),
if ($dec != '') then concat('.',$dec) else ()
)
};
Use:
let $n := 5573652.23
return
concat(local:format-int(xs:int(floor($n))),
'.',
substring(string($n - floor($n)), 3)
)
This produces exactly the wanted, correct result:
5,573,652.23
This doesn't work for you?:
format-number(5573652.23,",###.##")
You can play with this here. I am pretty sure that saxon supports this function.
Edit: This function is not supported in saxon (see comments below).
With XQuery 3.0 and Saxon-HE 9.7 Parser you can do the following:
declare decimal-format local:de decimal-separator = "," grouping-separator = ".";
declare decimal-format local:en decimal-separator = "." grouping-separator = ",";
let $numbers := (1234.567, 789, 1234567.765)
for $i in $numbers
return (
format-number($i,"#.###,##","local:de"),
format-number($i,"#,###.##","local:en")
)
The output is:
<?xml version="1.0" encoding="UTF-8"?>1.234,57 1,234.57 789,0 789.0 1.234.567,76
1,234,567.76

SQL Convert Seconds into Day:Hour:Min:Sec

Topic says it all. I'm trying to do some magic, via a function, that turns a second integer into a string "DD:HH:MM:SS".
Snip
input: 278543
output: "3D 5H 22M 23S "
What I'd like to do, more gracefully if possible, is pad the numbers (So that 5M shows as 05M) and right align them so that "3D 5H 22M 23S " is " 3D 5H 22M 23S" instead.
edit: Latest cut that seems to work. Would love to have it prettier, but this definitely works as far as I can tell:
CREATE FUNCTION DHMS(secondsElapsed INT)
RETURNS Char(20)
LANGUAGE SQL
NO EXTERNAL ACTION
DETERMINISTIC
BEGIN
DECLARE Dy Integer;
DECLARE Hr Integer;
DECLARE Mn Integer;
DECLARE Sc Integer;
SET Dy = Cast( secondsElapsed / 86400 as Int);
SET Hr = Cast(MOD( secondsElapsed, 86400 ) / 3600 as Int);
SET Mn = Cast(MOD( secondsElapsed, 3600 ) / 60 as Int);
SET Sc = Cast(MOD( secondsElapsed, 60 ) as Int);
RETURN REPEAT(' ',6-LENGTH(RTRIM(CAST(Dy AS CHAR(6))))) || Dy || 'D '
|| REPEAT('0',2-LENGTH(RTRIM(CAST(Hr AS CHAR(6))))) || Hr || 'H '
|| REPEAT('0',2-LENGTH(RTRIM(CAST(Mn AS CHAR(6))))) || Mn || 'M '
|| REPEAT('0',2-LENGTH(RTRIM(CAST(Sc AS CHAR(6))))) || Sc || 'S';
END
You were on the right track using LPAD(), since it can pad with zero or any other string. CHAR(15) is not enough to format the output the way you want and still allow five positions for the number of days, which is the length you specified in your code.
CREATE OR REPLACE FUNCTION DHMS(secondsElapsed INT)
RETURNS Char(18)
LANGUAGE SQL
NO EXTERNAL ACTION
DETERMINISTIC
RETURN LPAD( secondsElapsed / 86400 , 5 ) || 'D '
|| LPAD( MOD( secondsElapsed, 86400 ) / 3600, 2, '0') || 'H '
|| LPAD( MOD( secondsElapsed, 3600 ) / 60, 2, '0' ) || 'M '
|| LPAD( MOD( secondsElapsed, 60 ), 2, '0' ) || 'S'
;
declare #seconds int
set #seconds = 900000000
select cast(#seconds/86400 as varchar(50))+':'+Convert(VarChar, DateAdd(S, #seconds, 0), 108)

XQuery wrap result in computed node

I am trying to do some simple pagination in XQuery. I would like my root element of the returned XML to have (as attributes) various properties about the pagination (current page etc).
However I can't seem to find a way to add these dynamic attributes to my root element.
I've tried playing with the
element name {expr} and attribute name {expr}
functions, but can't seem to get them to work.
<result>{
let $results :=
for $item in doc('mydoc')/root/item
return $item
let $requested-page-nbr := 2
let $items-per-page := 10
let $count := count($results)
let $last-page-nbr := fn:ceiling($count div $items-per-page)
let $actual-page-nbr := if ($requested-page-nbr gt $last-page-nbr) then $last-page-nbr else $requested-page-nbr
let $start-item := $items-per-page * $actual-page-nbr - ( $items-per-page - 1 )
let $natural-end-item := $actual-page-nbr * $items-per-page
let $actual-end-item := if ($count ge $natural-end-item) then $natural-end-item else $count
for $j in ($start-item to $actual-end-item )
let $current := item-at($results, $j)
return
<document-summary
requested-page-nbr="{$requested-page-nbr}"
items-per-page="{$items-per-page}"
count="{$count}"
last-page-nbr="{$last-page-nbr}"
actual-page-nbr="{$actual-page-nbr}"
start-item="{$start-item}"
natural-end-item="{$natural-end-item}"
actual-end-item="{$actual-end-item}">
{($current)}
</document-summary>
}</result>
to add an attribute to the root:
<result>{attribute page {3}}</result>
in your case you probably want to do something like: (?)
...
return (
attribute page {$actual-page-nbr},
for $j in ($start-item to $actual-end-item )
let $current := item-at($results, $j)
return
<document-summary
requested-page-nbr="{$requested-page-nbr}"
items-per-page="{$items-per-page}"
count="{$count}"
last-page-nbr="{$last-page-nbr}"
actual-page-nbr="{$actual-page-nbr}"
start-item="{$start-item}"
natural-end-item="{$natural-end-item}"
actual-end-item="{$actual-end-item}">
{($current)}
</document-summary>)
...
does that answer your question?
I don't think that is the proper XQuery way...
This XQuery:
declare variable $requested-page-nbr external;
declare variable $items-per-page external;
declare variable $items := /root/item;
declare variable $firsties := $items[position() mod $items-per-page = 1];
for $first in $firsties
let $actual-page-nbr := index-of($firsties,$first)
let $group := $first|
$first/following-sibling::item[position() < $items-per-page]
let $previous := ($actual-page-nbr - 1) * $items-per-page
where $actual-page-nbr = $requested-page-nbr
return
<result>
<document-summary requested-page-nbr="{$requested-page-nbr}"
items-per-page="{$items-per-page}"
count="{count($items)}"
last-page-nbr="{count($firsties)}"
actual-page-nbr="{$actual-page-nbr}"
start-item="{$previous + 1}"
natural-end-item="{$previous + $items-per-page}"
actual-end-item="{$previous + count($group)}">{
$group
}</document-summary>
</result>
With this input:
<root>
<item>1</item>
<item>2</item>
<item>3</item>
<item>4</item>
<item>5</item>
<item>6</item>
<item>7</item>
<item>8</item>
<item>9</item>
<item>10</item>
<item>11</item>
<item>12</item>
<item>13</item>
</root>
With $requested-page-nbr set to 2 and $items-per-page set to 3, output:
<result>
<document-summary requested-page-nbr="2"
items-per-page="3"
count="13"
last-page-nbr="5"
actual-page-nbr="2"
start-item="4"
natural-end-item="6"
actual-end-item="6">
<item>4</item>
<item>5</item>
<item>6</item>
</document-summary>
</result>
With $requested-page-nbr set to 4 and $items-per-page set to 4, output:
<result>
<document-summary requested-page-nbr="4"
items-per-page="4"
count="13"
last-page-nbr="4"
actual-page-nbr="4"
start-item="13"
natural-end-item="16"
actual-end-item="13">
<item>13</item>
</document-summary>
</result>
As for returning a "page" of results, where you are using
{($current)}
we are using something like the following
{ subsequence($results, $start-item, $items-per-page) }

Resources