2
* Copyright 2006-2008 The FLWOR Foundation.
4
* Licensed under the Apache License, Version 2.0 (the "License");
5
* you may not use this file except in compliance with the License.
6
* You may obtain a copy of the License at
8
* http://www.apache.org/licenses/LICENSE-2.0
10
* Unless required by applicable law or agreed to in writing, software
11
* distributed under the License is distributed on an "AS IS" BASIS,
12
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
* See the License for the specific language governing permissions and
14
* limitations under the License.
30
#include "context/dynamic_context.h"
31
#include "context/static_context.h"
32
#include "runtime/core/arithmetic_impl.h"
33
#include "runtime/visitors/planiter_visitor.h"
34
#include "store/api/item.h"
35
#include "store/api/item_factory.h"
36
#include "store/api/store.h"
37
#include "system/globalenv.h"
38
#include "util/ascii_util.h"
39
#include "util/stream_util.h"
40
#include "util/string_util.h"
41
#include "util/time_util.h"
42
#include "util/utf8_util.h"
43
#include "zorbatypes/datetime.h"
44
#include "zorbatypes/datetime/parse.h"
45
#include "zorbatypes/duration.h"
46
#include "zorbatypes/zstring.h"
47
#include "zorbautils/locale.h"
50
#include "format_dateTime.h"
53
using namespace zorba::locale;
54
using namespace zorba::time;
58
SERIALIZABLE_CLASS_VERSIONS(FnFormatDateTimeIterator)
59
NARY_ACCEPT(FnFormatDateTimeIterator);
61
///////////////////////////////////////////////////////////////////////////////
64
* Holds presentation modifier data.
68
arabic, // '1' : 0 1 2 ... 10 11 12 ...
69
alpha, // 'a' : a b c ... z aa ab ac ...
70
ALPHA, // 'A' : A B C ... Z AA AB AC ...
71
roman, // 'i' : i ii iii iv v vi vii viii ix x ...
72
ROMAN, // 'I' : I II III IV V VI VII VIII IX X ...
76
words, // 'w' : one two three four ...
77
Words, // 'Ww': One Two Three Four ...
78
WORDS, // 'W' : ONE TWO THREE FOUR ...
79
military_tz // 'Z' : A B C ... J ... X Y Z
84
cardinal, // 'c': 7 or seven
85
ordinal // 'o': 7th or seventh
94
typedef unsigned width_type;
100
bool has_grouping_separators;
101
unicode::code_point zero;
105
second_co_type co_type;
107
second_at_type at_type;
110
width_type min_width;
111
width_type max_width;
114
// This stuff isn't part of the "presentation modifier" as discussed in the
115
// XQuery3.0 F&O spec, but this is a convenient place to put it nonetheless.
118
bool lang_is_fallback;
119
iso3166_1::type country;
121
bool cal_is_fallback;
123
void append_if_fallback_lang( zstring *s ) const {
124
if ( lang_is_fallback ) {
126
// XQuery 3.0 F&O: 9.8.4.3: If the fallback representation uses a
127
// different language from that requested, the output string must
128
// identify the language actually used, for example by prefixing the
129
// string with [Language: Y] (where Y is the language actually used)
130
// localized in an implementation-dependent way.
133
// TODO: localize "Language"
134
oss << "[Language: " << lang << ']';
139
bool gt_max_width( width_type n ) const {
140
return max_width > 0 && n > max_width;
143
zstring const& left_pad_zero( zstring *s ) const {
145
utf8::left_pad( s, min_width, first.zero );
149
zstring const& right_pad_space( zstring *s ) const {
151
utf8::right_pad( s, min_width, ' ' );
155
void set_default_width( width_type width ) {
156
if ( !(first.parsed || min_width || max_width) )
157
min_width = max_width = width;
161
first.parsed = false;
163
first.has_grouping_separators = false;
165
second.co_type = cardinal;
166
second.at_type = no_second_at;
167
min_width = max_width = 0;
171
///////////////////////////////////////////////////////////////////////////////
173
zstring alpha( unsigned n, bool capital ) {
176
char const c = capital ? 'A' : 'a';
178
unsigned const m = n - 1;
179
result.insert( (zstring::size_type)0, 1, c + m % 26 );
187
///////////////////////////////////////////////////////////////////////////////
189
namespace english_impl {
191
// Based on code from:
192
// http://www.cprogramming.com/challenges/integer-to-english-sol.html
194
static string const ones[][2] = {
198
{ "three", "third" },
199
{ "four", "fourth" },
202
{ "seven", "seventh" },
203
{ "eight", "eighth" },
206
{ "eleven", "eleventh" },
207
{ "twelve", "twelveth" },
208
{ "thirteen", "thirteenth" },
209
{ "fourteen", "fourteenth" },
210
{ "fifteen", "fifteenth" },
211
{ "sixteen", "sixteenth" },
212
{ "seventeen", "seventeenth" },
213
{ "eighteen", "eighteenth" },
214
{ "nineteen", "nineteenth" }
217
static zstring const tens[][2] = {
220
{ "twenty", "twentieth" },
221
{ "thirty", "thirtieth" },
222
{ "forty", "fortieth" },
223
{ "fifty", "fiftieth" },
224
{ "sixty", "sixtieth" },
225
{ "seventy", "seventieth" },
226
{ "eighty", "eighteenth" },
227
{ "ninety", "ninetieth" }
230
// Enough entries to print English for 64-bit integers.
231
static zstring const big[][2] = {
233
{ "thousand", "thousandth" },
234
{ "million", "millionth" },
235
{ "billion", "billionth" },
236
{ "trillion", "trillionth" },
237
{ "quadrillion", "quadrillionth" },
238
{ "quintillion", "quintillionth" }
241
inline zstring if_space( zstring const &s ) {
242
return s.empty() ? "" : ' ' + s;
245
static zstring hundreds( int64_t n, bool ordinal ) {
247
return ones[ n ][ ordinal ];
248
zstring const tmp( if_space( ones[ n % 10 ][ ordinal ] ) );
249
return tens[ n / 10 ][ ordinal && tmp.empty() ] + tmp;
252
} // namespace english_impl
255
* Converts a signed integer to English, e.g, 42 becomes "forty two".
257
* @param n The integer to convert.
258
* @param ordinal If \c true, ordinal words ("forty second") are returned.
259
* @return Returns \a n in English.
261
static zstring english( int64_t n, bool ordinal = false ) {
262
using namespace english_impl;
265
return ordinal ? "zeroth" : "zero";
267
bool const negative = n < 0;
272
bool big_ordinal = ordinal;
276
if ( int64_t const m = n % 1000 ) {
279
s = hundreds( m, ordinal );
281
zstring const tmp( if_space( hundreds( m % 100, ordinal ) ) );
282
s = ones[ m / 100 ][0] + ' '
283
+ (ordinal && tmp.empty() ? "hundredth" : "hundred") + tmp;
285
zstring const tmp( if_space( r ) );
286
r = s + if_space( big[ big_count ][ big_ordinal && tmp.empty() ] + tmp );
299
///////////////////////////////////////////////////////////////////////////////
301
static bool is_grouping_separator( unicode::code_point cp ) {
302
using namespace unicode;
304
// XQuery 3.0 F&O: 4.6.1: a grouping-separator-sign is a non-alphanumeric
305
// character, that is a character whose Unicode category is other than Nd,
306
// Nl, No, Lu, Ll, Lt, Lm or Lo.
308
return !( is_category( cp, Nd )
309
|| is_category( cp, Nl )
310
|| is_category( cp, No )
311
|| is_category( cp, Lu )
312
|| is_category( cp, Ll )
313
|| is_category( cp, Lt )
314
|| is_category( cp, Lm )
315
|| is_category( cp, Lo )
319
///////////////////////////////////////////////////////////////////////////////
322
* Returns the English ordinal suffix for an integer, e.g., "st" for 1, "nd"
325
* @param n The integer to return the ordinal suffix for.
326
* @return Returns said suffix.
328
static char const* ordinal( int n ) {
345
///////////////////////////////////////////////////////////////////////////////
348
* A unary_function to convert a (presumed) lower-case string to title-case
351
class to_title : public unary_function<char,char> {
353
to_title() : capitalize_( true ) { }
355
result_type operator()( argument_type c ) {
356
if ( ascii::is_alpha( c ) ) {
358
c = ascii::to_upper( c );
361
} else if ( ascii::is_space( c ) )
370
///////////////////////////////////////////////////////////////////////////////
372
static void append_number( int n, modifier const &mod, zstring *dest ) {
373
switch ( mod.first.type ) {
374
case modifier::arabic: {
375
utf8::itou_buf_type buf;
376
zstring tmp( utf8::itou( n, buf, mod.first.zero ) );
377
if ( mod.second.co_type == modifier::ordinal )
379
*dest += mod.left_pad_zero( &tmp );
383
case modifier::alpha:
384
case modifier::ALPHA: {
385
zstring tmp( alpha( n, mod.first.type == modifier::ALPHA ) );
386
*dest += mod.right_pad_space( &tmp );
390
case modifier::roman:
391
case modifier::ROMAN: {
393
if ( mod.first.type == modifier::ROMAN )
396
zstring tmp( oss.str() );
397
*dest += mod.right_pad_space( &tmp );
401
case modifier::words: {
402
zstring tmp( english( n, mod.second.co_type == modifier::ordinal ) );
403
*dest += mod.right_pad_space( &tmp );
407
case modifier::Words: {
408
zstring tmp( english( n, mod.second.co_type == modifier::ordinal ) );
409
std::transform( tmp.begin(), tmp.end(), tmp.begin(), to_title() );
410
*dest += mod.right_pad_space( &tmp );
414
case modifier::WORDS: {
415
zstring tmp( english( n, mod.second.co_type == modifier::ordinal ) );
416
ascii::to_upper( tmp );
417
*dest += mod.right_pad_space( &tmp );
422
/* handled elsewhere */;
426
static void append_fractional_seconds( int n, modifier const &mod,
428
switch ( mod.first.type ) {
429
case modifier::arabic:
430
if ( mod.min_width || mod.max_width ) {
431
if ( mod.max_width ) {
432
double const f = (double)n / DateTime::FRAC_SECONDS_UPPER_LIMIT;
433
double const p = ::pow( 10, mod.max_width );
434
n = (int)( f * p + 0.5 );
436
n = (int)( n * 1000.0 / DateTime::FRAC_SECONDS_UPPER_LIMIT );
438
ascii::itoa_buf_type buf;
439
zstring tmp( ascii::itoa( n, buf ) );
441
if ( tmp.size() < mod.min_width )
442
ascii::right_pad( &tmp, mod.min_width, '0' );
443
else if ( mod.min_width > 0 )
444
while ( tmp.size() > mod.min_width &&
445
tmp[ tmp.size() - 1 ] == '0' ) {
446
tmp = tmp.substr( 0, tmp.size() - 1 );
451
n = (int)( n * 1000.0 / DateTime::FRAC_SECONDS_UPPER_LIMIT );
454
append_number( n, mod, dest );
458
static void append_string( zstring const &s, modifier const &mod,
461
switch ( mod.first.type ) {
463
utf8::to_lower( s, &tmp );
465
case modifier::Name: {
466
utf8::to_upper( s.substr( 0, 1 ), &tmp );
468
utf8::to_lower( s.substr( 1 ), &tmp2 );
473
utf8::to_upper( s, &tmp );
478
*dest += mod.right_pad_space( &tmp );
481
static void append_month( unsigned mon, modifier const &mod, zstring *dest ) {
482
switch ( mod.first.type ) {
485
case modifier::NAME: {
486
zstring name( locale::get_month_name( mon, mod.lang, mod.country ) );
487
utf8_string<zstring> u_name( name );
488
if ( mod.gt_max_width( u_name.size() ) ) {
490
// XQuery 3.0 F&O: 9.8.4.1: If the full representation of the value
491
// exceeds the specified maximum width, then the processor should
492
// attempt to use an alternative shorter representation that fits
493
// within the maximum width. Where the presentation modifier is N, n,
494
// or Nn, this is done by abbreviating the name, using either
495
// conventional abbreviations if available, or crude right-truncation
498
name = locale::get_month_abbr( mon, mod.lang, mod.country );
499
if ( mod.gt_max_width( u_name.size() ) )
500
u_name = u_name.substr( 0, mod.max_width );
502
mod.append_if_fallback_lang( dest );
503
append_string( name, mod, dest );
507
append_number( mon + 1, mod, dest );
511
static void append_timezone( char component, TimeZone const &tz,
512
modifier const &mod, zstring *dest ) {
513
ascii::itoa_buf_type buf;
515
bool has_grouping_separators;
517
if ( mod.first.format.empty() ) {
519
has_grouping_separators = true;
521
format = mod.first.format;
522
has_grouping_separators = mod.first.has_grouping_separators;
525
int hour = tz.getHours();
526
int const min = std::abs( tz.getMinutes() );
528
switch ( mod.first.type ) {
531
// XQuery 3.0 F&O: 9.8.4.2: If the first presentation modifier is N, then
532
// the timezone is output (where possible) as a timezone name, for
533
// example EST or CET. The same timezone offset has different names in
534
// different places; it is therefore recommended that this option should
535
// be used only if a country code or Olson timezone name is supplied in
536
// the $place argument. In the absence of this information, the
537
// implementation may apply a default, for example by using the timezone
538
// names that are conventional in North America. If no timezone name can
539
// be identified, the timezone offset is output using the fallback format
544
case 0: tmp += "GMT"; goto append;
545
case -5: tmp += "EST"; goto append;
546
case -6: tmp += "CST"; goto append;
547
case -7: tmp += "MST"; goto append;
548
case -8: tmp += "PST"; goto append;
550
// TODO: use Olson timezone names
553
case modifier::military_tz:
555
// Ibid: If the first presentation modifier is Z, then the timezone is
556
// formatted as a military timezone letter, using the convention Z =
557
// +00:00, A = +01:00, B = +02:00, ..., M = +12:00, N = -01:00, O =
558
// -02:00, ... Y = -12:00.
560
if ( tz.timeZoneNotSet() ) {
562
// Ibid: The letter J (meaning local time) is used in the case of a
563
// value that does not specify a timezone offset.
568
if ( hour >= -12 && hour <= 12 && !min ) {
569
tmp += time::get_military_tz( hour );
573
// Ibid: Timezone offsets that have no representation in this system
574
// (for example Indian Standard Time, +05:30) are output as if the
575
// format 01:01 had been requested.
584
if ( component == 'z' ) {
586
// Ibid: When the component specifier is z, the output is the same as
587
// for component specifier Z, except that it is prefixed by the
588
// characters GMT or some localized equivalent. The prefix is omitted,
589
// however, in cases where the timezone is identified by name rather
590
// than by a numeric offset from UTC.
595
if ( mod.second.at_type == modifier::traditional && !hour && !min ) {
597
// Ibid: If the first presentation modifier is numeric, in any of the
598
// above formats, and the second presentation modifier is t, then a
599
// zero timezone offset (that is, UTC) is output as Z instead of a
600
// signed numeric value.
606
if ( tz.isNegative() )
607
tmp += '-', hour = std::abs( hour );
611
if ( has_grouping_separators ) {
613
// Ibid: If the first presentation modifier is numeric with a grouping-
614
// separator (for example 1:01 or 01.01), then the timezone offset is
615
// output in hours and minutes, separated by the grouping separator,
616
// even if the number of minutes is zero: for example +5:00 or +10.30.
618
int grouping_separators = 0;
619
bool got_digit = false;
620
int hm_width[] = { 0, 0 }; // hour/minute widths
621
utf8_string<zstring const> const u_format( format );
622
utf8_string<zstring> u_tmp( tmp );
624
FOR_EACH( utf8_string<zstring const>, i, u_format ) {
625
unicode::code_point const cp = *i;
626
if ( unicode::is_Nd( cp ) ) {
628
if ( grouping_separators < 2 )
629
++hm_width[ grouping_separators ];
632
if ( got_digit && is_grouping_separator( cp ) ) {
633
if ( ++grouping_separators == 1 ) {
634
zstring tmp2( utf8::itou( hour, buf, mod.first.zero ) );
635
tmp += utf8::left_pad( &tmp2, hm_width[0], mod.first.zero );
637
} else if ( grouping_separators )
638
grouping_separators = 99;
643
zstring tmp2( utf8::itou( min, buf, mod.first.zero ) );
644
tmp += utf8::left_pad( &tmp2, hm_width[1], mod.first.zero );
647
utf8_string<zstring const> const u_format( format );
648
utf8_string<zstring const>::size_type const u_size( u_format.size() );
652
// Ibid: If the first presentation modifier is numeric and comprises
653
// one or two digits with no grouping-separator (for example 1 or
654
// 01), then the timezone is formatted as a displacement from UTC in
655
// hours, preceded by a plus or minus sign: for example -5 or +03. If
656
// the actual timezone offset is not an integral number of hours,
657
// then the minutes part of the offset is appended, separated by a
658
// colon: for example +10:30 or -1:15.
660
zstring tmp2( utf8::itou( hour, buf, mod.first.zero ) );
661
tmp += utf8::left_pad( &tmp2, u_size, mod.first.zero );
663
tmp2 = utf8::itou( min, buf, mod.first.zero );
665
tmp += utf8::left_pad( &tmp2, 2, mod.first.zero );
671
// Ibid: If the first presentation modifier is numeric and comprises
672
// three or four digits with no grouping-separator, for example 001
673
// or 0001, then the timezone offset is shown in hours and minutes
674
// with no separator, for example -0500 or +1030.
676
int const hhmm = hour * 100 + min;
677
zstring tmp2( utf8::itou( hhmm, buf, mod.first.zero ) );
678
tmp += utf8::left_pad( &tmp2, u_size, mod.first.zero );
688
static void append_weekday( unsigned mday, unsigned mon, unsigned year,
689
modifier const &mod, zstring *dest ) {
690
int wday = time::calc_wday( mday, mon, year );
692
modifier mod_copy( mod );
693
if ( !mod.first.parsed )
694
mod_copy.first.type = modifier::name;
696
switch ( mod_copy.first.type ) {
699
case modifier::NAME: {
700
zstring name( locale::get_weekday_name( wday, mod.lang, mod.country ) );
701
utf8_string<zstring> u_name( name );
702
if ( mod.gt_max_width( u_name.size() ) ) {
704
// XQuery 3.0 F&O: 9.8.4.1: If the full representation of the value
705
// exceeds the specified maximum width, then the processor should
706
// attempt to use an alternative shorter representation that fits
707
// within the maximum width. Where the presentation modifier is N, n,
708
// or Nn, this is done by abbreviating the name, using either
709
// conventional abbreviations if available, or crude right-truncation
712
name = locale::get_weekday_abbr( wday, mod.lang, mod.country );
713
if ( mod.gt_max_width( u_name.size() ) )
714
u_name = u_name.substr( 0, mod.max_width );
716
mod.append_if_fallback_lang( dest );
717
append_string( name, mod_copy, dest );
721
int const new_wday = calendar::convert_wday_to( wday, mod.cal );
722
if ( mod.cal_is_fallback || new_wday == -1 ) {
724
// Ibid: If the fallback representation uses a different calendar from
725
// that requested, the output string must identify the calendar
726
// actually used, for example by prefixing the string with [Calendar:
727
// X] (where X is the calendar actually used), localized as appropriate
728
// to the requested language.
731
// TODO: localize "Calendar"
733
<< ( new_wday == -1 ? calendar::get_default() : mod.cal ) << ']';
737
append_number( wday, mod_copy, dest );
742
static void append_week_in_year( unsigned mday, unsigned mon, unsigned year,
743
modifier const &mod, zstring *dest ) {
744
int week = time::calendar::calc_week_in_year( mday, mon, year, mod.cal );
746
week = time::calendar::calc_week_in_year( mday, mon, year, calendar::ISO );
748
// TODO: localize "Calendar"
749
oss << "[Calendar: " << calendar::string_of[ calendar::ISO ] << ']';
752
append_number( week, mod, dest );
755
static void append_year( int year, modifier const &mod, zstring *s ) {
757
append_number( year, mod, &tmp );
759
if ( mod.first.type == modifier::arabic ) {
760
utf8_string<zstring> u_tmp( tmp );
761
utf8_string<zstring>::size_type const u_size = u_tmp.size();
762
if ( mod.gt_max_width( u_size ) ) {
764
// XQuery 3.0 F&O: 9.8.4.1: If the full representation of the value
765
// exceeds the specified maximum width, then the processor should attempt
766
// to use an alternative shorter representation that fits within the
767
// maximum width. ... In the case of the year component, setting
768
// max-width requests omission of high-order digits from the year, for
769
// example, if max-width is set to 2 then the year 2003 will be output as
772
u_tmp = u_tmp.substr( u_size - mod.max_width );
778
static void parse_first_modifier( zstring const &picture_str,
779
zstring::const_iterator *i,
780
modifier *mod, QueryLoc const &loc ) {
781
zstring::const_iterator &j = *i;
782
ascii::skip_whitespace( picture_str, &j );
783
if ( j == picture_str.end() || *j == ',' ) {
785
// Assume that the ',' is the start of the width modifier (hence there is
786
// neither a first nor second modifier).
791
utf8_string<zstring const> const u_picture_str( picture_str );
792
utf8_string<zstring const>::const_iterator u( u_picture_str.current( j ) );
793
utf8_string<zstring> u_mod_format( mod->first.format );
794
unicode::code_point cp = *u;
796
if ( cp != '#' && is_grouping_separator( cp ) ) {
798
// XQuery 3.0 F&O: 4.6.1: A grouping-separator-sign must not appear
799
// at the start ... of the decimal-digit-pattern ....
801
throw XQUERY_EXCEPTION(
805
ZED( FOFD1340_NoGroupSepAtStart_3 ),
806
unicode::printable_cp( cp )
815
// Ibid: if a variable marker contains one or more commas, then the last
816
// comma is treated as introducing the width modifier, and all others are
817
// treated as grouping separators.
819
// we have to count the number of commas in order to know when we've reached
823
for ( zstring::const_iterator c( *i ); c != picture_str.end(); ++c )
827
unicode::code_point zero[2];
829
if ( cp == '#' || unicode::is_Nd( cp, &zero[0] ) ) {
830
bool got_grouping_separator = false;
831
bool got_mandatory_digit = cp != '#';
834
while ( ++u != u_picture_str.end() ) {
837
if ( got_mandatory_digit ) {
839
// Ibid: There may be zero or more optional-digit-signs, and (if
840
// present) these must precede all mandatory-digit-signs.
842
throw XQUERY_EXCEPTION(
846
ZED( FOFD1340_NoOptDigitAfterMandatory )
851
got_grouping_separator = false;
852
} else if ( unicode::is_Nd( cp, &zero[ got_mandatory_digit ] ) ) {
853
if ( got_mandatory_digit ) {
854
if ( zero[1] != zero[0] ) {
856
// Ibid: All mandatory-digit-signs within the format token must be
857
// from the same digit family, where a digit family is a sequence
858
// of ten consecutive characters in Unicode category Nd, having
859
// digit values 0 through 9.
861
throw XQUERY_EXCEPTION(
865
ZED( FOFD1340_DigitNotSameFamily_34 ),
866
unicode::printable_cp( cp ),
867
unicode::printable_cp( zero[1] )
873
// Ibid: A format token containing more than one digit, such as 001
874
// or 9999, sets the minimum and maximum width to the number of
875
// digits appearing in the format token.
877
if ( !mod->min_width )
878
mod->min_width = mod->max_width = 2;
880
mod->min_width = ++mod->max_width;
882
got_mandatory_digit = true;
883
got_grouping_separator = false;
884
} else if ( cp == ';' || cp == ']' )
886
else if ( unicode::is_space( cp ) )
888
else if ( is_grouping_separator( cp ) ) {
889
if ( cp == ',' && !--commas ) {
891
// Ibid: if a variable marker contains one or more commas, then the
892
// last comma is treated as introducing the width modifier, and all
893
// others are treated as grouping separators.
897
if ( got_grouping_separator ) {
899
// Ibid: A grouping-separator-sign must not appear ... adjacent to
900
// another grouping-separator-sign.
902
throw XQUERY_EXCEPTION(
906
ZED( FOFD1340_NoAdjacentGroupSep_3 ),
907
unicode::printable_cp( cp )
912
got_grouping_separator = true;
913
mod->first.has_grouping_separators = true;
919
if ( got_grouping_separator ) {
921
// Ibid: A grouping-separator-sign must not appear at the ... end of the
922
// decimal-digit-pattern ....
924
throw XQUERY_EXCEPTION(
928
ZED( FOFD1340_NoGroupSepAtEnd_3 ),
929
unicode::printable_cp( cp )
934
if ( !got_mandatory_digit ) {
936
// Ibid: There must be at least one mandatory-digit-sign.
938
throw XQUERY_EXCEPTION(
940
ERROR_PARAMS( picture_str, ZED( FOFD1340_MustBeOneMandatoryDigit ) ),
944
mod->first.zero = zero[0];
949
mod->first.type = modifier::ALPHA;
952
mod->first.type = modifier::alpha;
955
mod->first.type = modifier::ROMAN;
958
mod->first.type = modifier::roman;
961
if ( j != picture_str.end() && *j == 'n' )
962
mod->first.type = modifier::Name, ++j;
964
mod->first.type = modifier::NAME;
967
mod->first.type = modifier::name;
970
if ( j != picture_str.end() && *j == 'w' )
971
mod->first.type = modifier::Words, ++j;
973
mod->first.type = modifier::WORDS;
976
mod->first.type = modifier::words;
979
mod->first.type = modifier::military_tz;
983
// Ibid: If an implementation does not support a numbering sequence
984
// represented by the given token, it must use a format token of 1.
986
mod->first.type = modifier::arabic;
989
mod->first.parsed = true;
992
static void parse_second_modifier( zstring const &picture_str,
993
zstring::const_iterator *i, modifier *mod,
994
QueryLoc const &loc ) {
995
zstring::const_iterator &j = *i;
996
ascii::skip_whitespace( picture_str, &j );
997
if ( j == picture_str.end() )
1000
case 'c': mod->second.co_type = modifier::cardinal ; break;
1001
case 'o': mod->second.co_type = modifier::ordinal ; break;
1002
case 'a': mod->second.at_type = modifier::alphabetic ; ++j; return;
1003
case 't': mod->second.at_type = modifier::traditional; ++j; return;
1006
if ( ++j == picture_str.end() )
1010
if ( ++j == picture_str.end() )
1011
throw XQUERY_EXCEPTION(
1013
ERROR_PARAMS( picture_str, ZED( CharExpected_3 ), ')' ),
1018
mod->second.co_string += *j;
1024
static void parse_width_modifier( zstring const &picture_str,
1025
zstring::const_iterator *i, modifier *mod,
1026
QueryLoc const &loc ) {
1027
zstring::const_iterator &j = *i;
1029
ascii::skip_whitespace( picture_str, &j );
1030
if ( j == picture_str.end() || (*j != ',' && *j != ';') )
1032
ascii::skip_whitespace( picture_str, &++j );
1033
if ( j == picture_str.end() )
1034
goto bad_width_modifier;
1040
mod->min_width = static_cast<modifier::width_type>(
1041
ztd::atoull( j, picture_str.end(), &j )
1044
catch ( std::exception const& ) {
1045
goto bad_width_modifier;
1051
ascii::skip_whitespace( picture_str, &j );
1052
if ( j == picture_str.end() || *j != '-' )
1054
ascii::skip_whitespace( picture_str, &++j );
1055
if ( j == picture_str.end() )
1056
goto bad_width_modifier;
1061
mod->max_width = static_cast<modifier::width_type>(
1062
ztd::atoull( j, picture_str.end(), &j )
1065
catch ( std::exception const& ) {
1066
goto bad_width_modifier;
1073
throw XQUERY_EXCEPTION(
1075
ERROR_PARAMS( picture_str, ZED( FOFD1340_BadWidthModifier ) ),
1080
static int get_data_type( char component ) {
1081
switch ( component ) {
1082
case 'D': return DateTime::DAY_DATA;
1083
case 'd': return DateTime::DAY_DATA;
1084
case 'E': return DateTime::YEAR_DATA;
1085
case 'F': return DateTime::DAY_DATA;
1086
case 'f': return DateTime::FRACSECONDS_DATA;
1087
case 'H': return DateTime::HOUR_DATA;
1088
case 'h': return DateTime::HOUR_DATA;
1089
case 'm': return DateTime::MINUTE_DATA;
1090
case 'M': return DateTime::MONTH_DATA;
1091
case 'P': return DateTime::HOUR_DATA;
1092
case 's': return DateTime::SECONDS_DATA;
1093
case 'W': return DateTime::DAY_DATA;
1094
case 'w': return DateTime::DAY_DATA;
1095
case 'Y': return DateTime::YEAR_DATA;
1096
default : return -1;
1100
bool FnFormatDateTimeIterator::nextImpl( store::Item_t& result,
1101
PlanState &planState ) const {
1102
zstring picture_str, result_str, item_str;
1103
xs_dateTime dateTime;
1104
calendar::type cal = calendar::unknown;
1105
iso639_1::type lang = iso639_1::unknown;
1106
iso3166_1::type country = iso3166_1::unknown;
1107
bool cal_is_fallback = false, lang_is_fallback = false;
1108
bool in_variable_marker;
1110
PlanIteratorState *state;
1112
DEFAULT_STACK_INIT( PlanIteratorState, state, planState);
1114
if ( !consumeNext( item, theChildren[0].getp(), planState ) ) {
1115
// Got the empty sequence -- return same
1116
STACK_PUSH( false, state );
1118
dateTime = item->getDateTimeValue();
1119
consumeNext( item, theChildren[1].getp(), planState );
1120
item->getStringValue2( picture_str );
1122
if ( theChildren.size() > 2 ) {
1123
consumeNext( item, theChildren[2].getp(), planState );
1124
if ( !locale::parse( item->getStringValue(), &lang, &country ) ||
1125
!locale::is_supported( lang, country ) ) {
1126
lang = iso639_1::unknown;
1127
lang_is_fallback = true;
1130
consumeNext( item, theChildren[3].getp(), planState );
1131
item->getStringValue2( item_str );
1132
// TODO: handle calendar being a QName.
1133
cal = calendar::find( item_str );
1135
cal_is_fallback = true;
1137
consumeNext( item, theChildren[4].getp(), planState );
1138
item->getStringValue2( item_str );
1139
// TODO: do something with place
1144
// XQuery 3.0 F&O: 9.8.4.3: If the $calendar argument is omitted or is
1145
// set to an empty sequence then the default calendar defined in the
1146
// dynamic context is used.
1148
cal = planState.theLocalDynCtx->get_calendar();
1153
// Ibid: If the $language argument is omitted or is set to an empty
1154
// sequence, or if it is set to an invalid value or a value that the
1155
// implementation does not recognize, then the processor uses the default
1156
// language defined in the dynamic context.
1158
planState.theLocalDynCtx->get_locale( &lang, &country );
1162
in_variable_marker = false;
1164
FOR_EACH( zstring, i, picture_str ) {
1165
if ( !in_variable_marker ) {
1168
if ( ztd::peek( picture_str, i ) == '[' )
1172
in_variable_marker = true;
1177
if ( ztd::peek( picture_str, i ) == ']' )
1185
if ( ascii::is_space( *i ) )
1186
continue; // ignore all whitespace
1191
throw XQUERY_EXCEPTION(
1193
ERROR_PARAMS( picture_str, ZED( FOFD1340_NoComponent ) ),
1197
in_variable_marker = false;
1218
throw XQUERY_EXCEPTION(
1221
picture_str, ZED( FOFD1340_MultipleComponent_3 ), *i
1229
throw XQUERY_EXCEPTION(
1231
ERROR_PARAMS( picture_str, ZED( FOFD1340_BadComponent_3 ), *i ),
1235
if ( ++i == picture_str.end() )
1240
mod.lang_is_fallback = lang_is_fallback;
1241
mod.country = country;
1243
mod.cal_is_fallback = cal_is_fallback;
1246
parse_first_modifier( picture_str, &i, &mod, loc );
1247
if ( i == picture_str.end() )
1250
parse_second_modifier( picture_str, &i, &mod, loc );
1251
if ( i == picture_str.end() )
1253
parse_width_modifier( picture_str, &i, &mod, loc );
1254
if ( i == picture_str.end() )
1261
int const data_type = get_data_type( component );
1262
if ( data_type != -1 && !DateTime::FACET_MEMBERS[facet_type][data_type] )
1263
throw XQUERY_EXCEPTION(
1264
err::FOFD1350, ERROR_PARAMS( component ), ERROR_LOC( loc )
1267
switch ( component ) {
1268
case 'C': { // calendar
1269
modifier mod_copy( mod );
1270
if ( !mod.first.parsed )
1271
mod_copy.first.type = modifier::name;
1272
append_string( "gregorian", mod_copy, &result_str );
1276
append_number( dateTime.getDay(), mod, &result_str );
1279
append_number( dateTime.getDayOfYear(), mod, &result_str );
1282
modifier mod_copy( mod );
1283
if ( !mod.first.parsed )
1284
mod_copy.first.type = modifier::name;
1285
int const year = dateTime.getYear();
1286
zstring const era( year > 0 ? "ad" : year < 0 ? "bc" : "" );
1287
append_string( era, mod_copy, &result_str );
1291
modifier mod_copy( mod );
1292
if ( !mod.first.parsed )
1293
mod_copy.first.type = modifier::name;
1295
dateTime.getDay(), dateTime.getMonth() - 1, dateTime.getYear(),
1296
mod_copy, &result_str
1301
append_fractional_seconds(
1302
dateTime.getFractionalSeconds(), mod, &result_str
1305
case 'H': // hour (24 hours)
1306
append_number( dateTime.getHours(), mod, &result_str );
1308
case 'h': // hour (12 hours)
1309
// Convert hour from: 0 1 ... 12 13 ... 23
1310
// to: 12 1 ... 12 1 ... 11
1312
1 + (11 + dateTime.getHours()) % 12, mod, &result_str
1316
append_month( dateTime.getMonth() - 1, mod, &result_str );
1319
modifier mod_copy( mod );
1320
mod_copy.set_default_width( 2 );
1321
append_number( dateTime.getMinutes(), mod_copy, &result_str );
1325
modifier mod_copy( mod );
1326
if ( !mod.first.parsed )
1327
mod_copy.first.type = modifier::name;
1329
locale::get_time_ampm( dateTime.getHours() >= 12, lang, country ),
1330
mod_copy, &result_str
1335
modifier mod_copy( mod );
1336
mod_copy.set_default_width( 2 );
1337
append_number( dateTime.getIntSeconds(), mod_copy, &result_str );
1341
append_week_in_year(
1342
dateTime.getDay(), dateTime.getMonth() - 1, dateTime.getYear(),
1347
append_number( dateTime.getWeekInMonth(), mod, &result_str );
1350
append_year( std::abs( dateTime.getYear() ), mod, &result_str );
1355
component, dateTime.getTimezone(), mod, &result_str
1361
if ( in_variable_marker )
1362
eos: throw XQUERY_EXCEPTION(
1364
ERROR_PARAMS( picture_str, ZED( CharExpected_3 ), ']' ),
1368
STACK_PUSH( GENV_ITEMFACTORY->createString( result, result_str ), state );
1374
///////////////////////////////////////////////////////////////////////////////
1376
} // namespace zorba
1377
/* vim:set et sw=2 ts=2: */