|
xquery version "1.0-ml"; |
|
|
|
module namespace zi = 'http://pressassociation.com/std/lib/timezone/parse'; |
|
|
|
(: Compile a local UNIX zoneinfo file into TimeZoneInfo documents suitable for timezone.xqy library use. |
|
|
|
To generate TimeZoneInfo documents: |
|
|
|
import module zi = 'http://pressassociation.com/std/lib/timezone/parse' at '/lib/marklogic-commons/parse-zoneinfo.xqy'; |
|
zi:parse-zoneinfo('Asia/Tokyo') |
|
|
|
To generate *and* insert TimeZoneInfo documents into current database with unique URIs (e.g. for use by timezone.xqy): |
|
|
|
zi:insert-timezoneinfo(zi:parse-zoneinfo('Asia/Tokyo')) |
|
:) |
|
|
|
declare namespace tz = 'http://pressassociation.com/std/lib/timezone'; |
|
|
|
declare variable $g_fsZoneInfoPath := '/usr/share/zoneinfo/'; (: input zoneinfo filesystem path :) |
|
declare variable $g_dbZoneInfoURI := '/zoneinfo/'; (: output db URI prefix :) |
|
|
|
declare function zi:parse-zoneinfo($timezone-name as xs:string) as element(tz:TimeZoneInfo)* { |
|
let $zoneinfo := xdmp:external-binary(fn:concat($g_fsZoneInfoPath,$timezone-name)) |
|
let $version := zi:unsigned-byte-at($zoneinfo,5) |
|
where xdmp:binary-decode(xdmp:subbinary($zoneinfo,1,4),'ISO-8859-1') = 'TZif' and $version = (0,50) |
|
return |
|
(: let $tzh_ttisgmtcnt := zi:unsigned-int-at($zoneinfo,21) - ref-only - not required :) |
|
(: let $tzh_ttisstdcnt := zi:unsigned-int-at($zoneinfo,25) - ref-only - not required :) |
|
(: let $tzh_leapcnt := zi:unsigned-int-at($zoneinfo,29) - ref-only - not required :) |
|
let $tzh_timecnt := zi:unsigned-int-at($zoneinfo,33) |
|
let $tzh_typecnt := zi:unsigned-int-at($zoneinfo,37) |
|
(: let $tzh_charcnt := zi:unsigned-int-at($zoneinfo,41) - ref-only - not required :) |
|
let $changeTimes := |
|
for $intTime in zi:int-sequence-starting-at($zoneinfo,45,$tzh_timecnt) |
|
return zi:int-to-dateTime($intTime) |
|
|
|
let $localTimeIndex := zi:unsigned-byte-sequence-starting-at($zoneinfo,45+$tzh_timecnt*4,$tzh_timecnt) |
|
let $ttinfoStart := 45+$tzh_timecnt*4+$tzh_timecnt |
|
let $ttinfos := zi:parse-ttinfo($zoneinfo,$ttinfoStart,$tzh_typecnt) |
|
return ( |
|
for $i in (1 to $tzh_timecnt) |
|
let $ttinfo := $ttinfos[$localTimeIndex[$i]+1] |
|
return element tz:TimeZoneInfo { |
|
element tz:TimeZone { $timezone-name }, |
|
element tz:Name { fn:data($ttinfo/tt_abbr) }, |
|
element tz:Abbreviation { fn:data($ttinfo/tt_abbr) }, |
|
element tz:DST { fn:data($ttinfo/tt_isdst) }, |
|
element tz:OffsetToUTC { |
|
xs:dayTimeDuration(fn:concat($ttinfo/tt_gmtoff[.<0]/fn:concat('-'),'PT',fn:abs($ttinfo/tt_gmtoff),'S')) |
|
}, |
|
element tz:DateTimeRange { |
|
element tz:Starts { |
|
$changeTimes[$i] |
|
}, |
|
element tz:Ends { |
|
(: NB last value in zoneinfo lasts until the end of (UNIX 32-bit) time; important where there is no DST, e.g. Asia/Tokyo :) |
|
if(fn:exists($changeTimes[$i+1])) then $changeTimes[$i+1] else $g_endOfTime |
|
} |
|
} |
|
} |
|
) |
|
}; |
|
|
|
(: inserts given TimeZoneInfo documents into the database; returns document URIs :) |
|
declare function zi:insert-timezoneinfo($timezone-info as element(tz:TimeZoneInfo)*) as xs:string* { |
|
zi:insert-timezoneinfo($timezone-info, xdmp:default-permissions()) |
|
}; |
|
|
|
declare function zi:insert-timezoneinfo($timezone-info as element(tz:TimeZoneInfo)*, $permissions as element(sec:permission)*) as xs:string* { |
|
for $tzi in $timezone-info |
|
let $timezone-name := fn:data($tzi/tz:TimeZone) |
|
let $uri := fn:concat('/zoneinfo/',$timezone-name,'/',fn:string-join((fn:tokenize($timezone-name,'/')[fn:last()],fn:string(xs:date($tzi/tz:DateTimeRange/tz:Starts)),$tzi/tz:Abbreviation/fn:string()),'-'),'.xml') |
|
return ($uri,xdmp:document-insert($uri,$tzi,$permissions,'fixture')) |
|
}; |
|
|
|
(: PRIVATE :) |
|
|
|
(: signed 32-bit int conversion constants :) |
|
declare private variable $g_mask32 := xdmp:hex-to-integer('7FFFFFFF'); |
|
declare private variable $g_signedBit32 := xdmp:hex-to-integer('80000000'); |
|
|
|
(: time conversion constants :) |
|
declare private variable $g_wallclockMultiplier64 := 10000000; |
|
declare private variable $g_utcOffset := xs:dayTimeDuration('PT0H'); |
|
declare private variable $g_epoch := xs:dateTime('1970-01-01T00:00:00Z'); |
|
declare private variable $g_endOfTime := xs:dateTime('2038-01-19T03:14:07'); |
|
|
|
declare private function zi:unsigned-int-at($binary as binary(), $pos as xs:double) as xs:unsignedInt { |
|
xdmp:hex-to-integer(xs:string(xdmp:subbinary($binary,$pos,4))) |
|
}; |
|
|
|
declare private function zi:int-at($binary as binary(), $pos as xs:double) as xs:int { |
|
let $v := xdmp:hex-to-integer(xs:string(xdmp:subbinary($binary,$pos,4))) |
|
return if(xdmp:and64($v,$g_signedBit32)) then -(xdmp:and64(xdmp:not64($v),$g_mask32)+1) else $v |
|
}; |
|
|
|
declare private function zi:unsigned-byte-at($binary as binary(), $pos as xs:double) as xs:unsignedByte { |
|
xdmp:hex-to-integer(xs:string(xdmp:subbinary($binary,$pos,1))) |
|
}; |
|
|
|
declare private function zi:int-sequence-starting-at($binary as binary(), $start as xs:double, $count as xs:integer) as xs:int* { |
|
for $int in (1 to $count) |
|
return zi:int-at($binary,$start + ($int - 1) * 4) |
|
}; |
|
|
|
declare private function zi:unsigned-byte-sequence-starting-at($binary as binary(), $start as xs:double, $count as xs:integer) as xs:unsignedByte* { |
|
for $byte in (1 to $count) |
|
return zi:unsigned-byte-at($binary,$start + ($byte - 1)) |
|
}; |
|
|
|
declare private function zi:int-to-dateTime($int as xs:int) as xs:dateTime { |
|
if($int gt 0) then ( |
|
fn:adjust-dateTime-to-timezone(xdmp:timestamp-to-wallclock($int * $g_wallclockMultiplier64),$g_utcOffset) |
|
) else ( |
|
$g_epoch - xs:dayTimeDuration(fn:concat('PT',fn:abs($int),'S')) |
|
) |
|
}; |
|
|
|
declare private function zi:parse-ttinfo($binary as binary(), $start as xs:double, $count as xs:integer) as element(ttinfo)* { |
|
let $charstart := $start + 6*$count |
|
for $i in (1 to $count) |
|
let $pos := $start + ($i - 1) * 6 |
|
let $tt_gmtoff := zi:int-at($binary,$pos) |
|
let $tt_isdst := zi:unsigned-byte-at($binary,$pos+4) |
|
let $tt_abbrind := zi:unsigned-byte-at($binary,$pos+5) |
|
return element ttinfo { |
|
element tt_gmtoff { $tt_gmtoff }, |
|
element tt_isdst { fn:not(fn:not($tt_isdst)) }, |
|
element tt_abbr { |
|
xdmp:binary-decode(xdmp:subbinary($binary,$charstart+$tt_abbrind),'UTF-8') |
|
} |
|
} |
|
}; |