~zorba-coders/zorba/trunk

« back to all changes in this revision

Viewing changes to src/runtime/durations_dates_times/format_dateTime.cpp

  • Committer: Zorba Build Bot
  • Author(s): paul at lucasmail
  • Date: 2013-03-21 19:33:22 UTC
  • mfrom: (11255.2.76 bug-1123162)
  • Revision ID: chillery+buildbot@lambda.nu-20130321193322-6fdtcp5ldf1dhych
Fixed many date/time formatting bugs by completely rewriting the code; added basic support for multiple calendar systems; added default countries-for-languages look-up for locales. Approved: Matthias Brantner, Sorin Marian Nasoi, Nicolae Brinza, Paul J. Lucas

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * Copyright 2006-2008 The FLWOR Foundation.
 
3
 *
 
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
 
7
 *
 
8
 * http://www.apache.org/licenses/LICENSE-2.0
 
9
 *
 
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.
 
15
 */
 
16
 
 
17
#include "stdafx.h"
 
18
 
 
19
// standard
 
20
#include <algorithm>
 
21
#include <cctype>
 
22
#include <cmath>
 
23
#include <cstdlib>
 
24
#include <functional>
 
25
#include <iomanip>
 
26
#include <iostream>
 
27
#include <sstream>
 
28
 
 
29
// Zorba
 
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"
 
48
 
 
49
// local
 
50
#include "format_dateTime.h"
 
51
 
 
52
using namespace std;
 
53
using namespace zorba::locale;
 
54
using namespace zorba::time;
 
55
 
 
56
namespace zorba {
 
57
 
 
58
SERIALIZABLE_CLASS_VERSIONS(FnFormatDateTimeIterator)
 
59
NARY_ACCEPT(FnFormatDateTimeIterator);
 
60
 
 
61
///////////////////////////////////////////////////////////////////////////////
 
62
 
 
63
/**
 
64
 * Holds presentation modifier data.
 
65
 */
 
66
struct modifier {
 
67
  enum first_type {
 
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 ...
 
73
    name,         // 'n' : name
 
74
    Name,         // 'Nn': Name
 
75
    NAME,         // 'N' : NAME
 
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
 
80
  };
 
81
 
 
82
  enum second_co_type {
 
83
    no_second_co,
 
84
    cardinal,     // 'c': 7 or seven
 
85
    ordinal       // 'o': 7th or seventh
 
86
  };
 
87
 
 
88
  enum second_at_type {
 
89
    no_second_at,
 
90
    alphabetic,   // 'a'
 
91
    traditional   // 't'
 
92
  };
 
93
 
 
94
  typedef unsigned width_type;
 
95
 
 
96
  struct {
 
97
    bool parsed;
 
98
    first_type type;
 
99
    zstring format;
 
100
    bool has_grouping_separators;
 
101
    unicode::code_point zero;
 
102
  } first;
 
103
 
 
104
  struct {
 
105
    second_co_type co_type;
 
106
    zstring co_string;
 
107
    second_at_type at_type;
 
108
  } second;
 
109
 
 
110
  width_type min_width;
 
111
  width_type max_width;
 
112
 
 
113
  //
 
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.
 
116
  //
 
117
  iso639_1::type lang;
 
118
  bool lang_is_fallback;
 
119
  iso3166_1::type country;
 
120
  calendar::type cal;
 
121
  bool cal_is_fallback;
 
122
 
 
123
  void append_if_fallback_lang( zstring *s ) const {
 
124
    if ( lang_is_fallback ) {
 
125
      //
 
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.
 
131
      //
 
132
      ostringstream oss;
 
133
      // TODO: localize "Language"
 
134
      oss << "[Language: " << lang << ']';
 
135
      *s += oss.str();
 
136
    }
 
137
  }
 
138
 
 
139
  bool gt_max_width( width_type n ) const {
 
140
    return max_width > 0 && n > max_width;
 
141
  }
 
142
 
 
143
  zstring const& left_pad_zero( zstring *s ) const {
 
144
    if ( min_width )
 
145
      utf8::left_pad( s, min_width, first.zero );
 
146
    return *s;
 
147
  }
 
148
 
 
149
  zstring const& right_pad_space( zstring *s ) const {
 
150
    if ( min_width )
 
151
      utf8::right_pad( s, min_width, ' ' );
 
152
    return *s;
 
153
  }
 
154
 
 
155
  void set_default_width( width_type width ) {
 
156
    if ( !(first.parsed || min_width || max_width) )
 
157
      min_width = max_width = width;
 
158
  }
 
159
 
 
160
  modifier() {
 
161
    first.parsed = false;
 
162
    first.type = arabic;
 
163
    first.has_grouping_separators = false;
 
164
    first.zero = '0';
 
165
    second.co_type = cardinal;
 
166
    second.at_type = no_second_at;
 
167
    min_width = max_width = 0;
 
168
  };
 
169
};
 
170
 
 
171
///////////////////////////////////////////////////////////////////////////////
 
172
 
 
173
zstring alpha( unsigned n, bool capital ) {
 
174
  zstring result;
 
175
  if ( n ) {
 
176
    char const c = capital ? 'A' : 'a';
 
177
    while ( n ) {
 
178
      unsigned const m = n - 1;
 
179
      result.insert( (zstring::size_type)0, 1, c + m % 26 );
 
180
      n = m / 26;
 
181
    }
 
182
  } else
 
183
    result = "0";
 
184
  return result;
 
185
}
 
186
 
 
187
///////////////////////////////////////////////////////////////////////////////
 
188
 
 
189
namespace english_impl {
 
190
 
 
191
// Based on code from:
 
192
// http://www.cprogramming.com/challenges/integer-to-english-sol.html
 
193
 
 
194
static string const ones[][2] = {
 
195
  { "",          ""            },
 
196
  { "one",       "first"       },
 
197
  { "two",       "second"      },
 
198
  { "three",     "third"       },
 
199
  { "four",      "fourth"      },
 
200
  { "five",      "fifth"       },
 
201
  { "six",       "sixth"       },
 
202
  { "seven",     "seventh"     },
 
203
  { "eight",     "eighth"      },
 
204
  { "nine",      "ninth"       },
 
205
  { "ten",       "tenth"       },
 
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"  }
 
215
};
 
216
 
 
217
static zstring const tens[][2] = {
 
218
  { "",        ""           },
 
219
  { "",        ""           },
 
220
  { "twenty",  "twentieth"  },
 
221
  { "thirty",  "thirtieth"  },
 
222
  { "forty",   "fortieth"   },
 
223
  { "fifty",   "fiftieth"   },
 
224
  { "sixty",   "sixtieth"   },
 
225
  { "seventy", "seventieth" },
 
226
  { "eighty",  "eighteenth" },
 
227
  { "ninety",  "ninetieth"  }
 
228
};
 
229
 
 
230
// Enough entries to print English for 64-bit integers.
 
231
static zstring const big[][2] = {
 
232
  { "",            ""              },
 
233
  { "thousand",    "thousandth"    },
 
234
  { "million",     "millionth"     },
 
235
  { "billion",     "billionth"     },
 
236
  { "trillion",    "trillionth"    },
 
237
  { "quadrillion", "quadrillionth" },
 
238
  { "quintillion", "quintillionth" }
 
239
};
 
240
 
 
241
inline zstring if_space( zstring const &s ) {
 
242
  return s.empty() ? "" : ' ' + s;
 
243
}
 
244
 
 
245
static zstring hundreds( int64_t n, bool ordinal ) {
 
246
  if ( n < 20 )
 
247
    return ones[ n ][ ordinal ];
 
248
  zstring const tmp( if_space( ones[ n % 10 ][ ordinal ] ) );
 
249
  return tens[ n / 10 ][ ordinal && tmp.empty() ] + tmp;
 
250
}
 
251
 
 
252
} // namespace english_impl
 
253
 
 
254
/**
 
255
 * Converts a signed integer to English, e.g, 42 becomes "forty two".
 
256
 *
 
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.
 
260
 */
 
261
static zstring english( int64_t n, bool ordinal = false ) {
 
262
  using namespace english_impl;
 
263
 
 
264
  if ( !n )
 
265
    return ordinal ? "zeroth" : "zero";
 
266
 
 
267
  bool const negative = n < 0;
 
268
  if ( negative )
 
269
    n = -n;
 
270
 
 
271
  int big_count = 0;
 
272
  bool big_ordinal = ordinal;
 
273
  zstring r;
 
274
 
 
275
  while ( n ) {
 
276
    if ( int64_t const m = n % 1000 ) {
 
277
      zstring s;
 
278
      if ( m < 100 )
 
279
        s = hundreds( m, ordinal );
 
280
      else {
 
281
        zstring const tmp( if_space( hundreds( m % 100, ordinal ) ) );
 
282
        s = ones[ m / 100 ][0] + ' '
 
283
          + (ordinal && tmp.empty() ? "hundredth" : "hundred") + tmp;
 
284
      }
 
285
      zstring const tmp( if_space( r ) );
 
286
      r = s + if_space( big[ big_count ][ big_ordinal && tmp.empty() ] + tmp );
 
287
      big_ordinal = false;
 
288
    }
 
289
    n /= 1000;
 
290
    ++big_count;
 
291
    ordinal = false;
 
292
  }
 
293
 
 
294
  if ( negative )
 
295
    r = "negative " + r;
 
296
  return r;
 
297
}
 
298
 
 
299
///////////////////////////////////////////////////////////////////////////////
 
300
 
 
301
static bool is_grouping_separator( unicode::code_point cp ) {
 
302
  using namespace unicode;
 
303
  //
 
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.
 
307
  //
 
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 )
 
316
  );
 
317
}
 
318
 
 
319
///////////////////////////////////////////////////////////////////////////////
 
320
 
 
321
/**
 
322
 * Returns the English ordinal suffix for an integer, e.g., "st" for 1, "nd"
 
323
 * for 2, etc.
 
324
 *
 
325
 * @param n The integer to return the ordinal suffix for.
 
326
 * @return Returns said suffix.
 
327
 */
 
328
static char const* ordinal( int n ) {
 
329
  n = std::abs( n );
 
330
  switch ( n % 100 ) {
 
331
    case 11:
 
332
    case 12:
 
333
    case 13:
 
334
      break;
 
335
    default:
 
336
      switch ( n % 10 ) {
 
337
        case 1: return "st";
 
338
        case 2: return "nd";
 
339
        case 3: return "rd";
 
340
      }
 
341
  }
 
342
  return "th";
 
343
}
 
344
 
 
345
///////////////////////////////////////////////////////////////////////////////
 
346
 
 
347
/**
 
348
 * A unary_function to convert a (presumed) lower-case string to title-case
 
349
 * "Like This."
 
350
 */
 
351
class to_title : public unary_function<char,char> {
 
352
public:
 
353
  to_title() : capitalize_( true ) { }
 
354
 
 
355
  result_type operator()( argument_type c ) {
 
356
    if ( ascii::is_alpha( c ) ) {
 
357
      if ( capitalize_ ) {
 
358
        c = ascii::to_upper( c );
 
359
        capitalize_ = false;
 
360
      }
 
361
    } else if ( ascii::is_space( c ) )
 
362
      capitalize_ = true;
 
363
    return c;
 
364
  };
 
365
 
 
366
private:
 
367
  bool capitalize_;
 
368
};
 
369
 
 
370
///////////////////////////////////////////////////////////////////////////////
 
371
 
 
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 )
 
378
        tmp += ordinal( n );
 
379
      *dest += mod.left_pad_zero( &tmp );
 
380
      break;
 
381
    }
 
382
 
 
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 );
 
387
      break;
 
388
    }
 
389
 
 
390
    case modifier::roman:
 
391
    case modifier::ROMAN: {
 
392
      ostringstream oss;
 
393
      if ( mod.first.type == modifier::ROMAN )
 
394
        oss << uppercase;
 
395
      oss << roman( n );
 
396
      zstring tmp( oss.str() );
 
397
      *dest += mod.right_pad_space( &tmp );
 
398
      break;
 
399
    }
 
400
 
 
401
    case modifier::words: {
 
402
      zstring tmp( english( n, mod.second.co_type == modifier::ordinal ) );
 
403
      *dest += mod.right_pad_space( &tmp );
 
404
      break;
 
405
    }
 
406
 
 
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 );
 
411
      break;
 
412
    }
 
413
 
 
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 );
 
418
      break;
 
419
    }
 
420
 
 
421
    default:
 
422
      /* handled elsewhere */;
 
423
  }
 
424
}
 
425
 
 
426
static void append_fractional_seconds( int n, modifier const &mod,
 
427
                                       zstring *dest ) {
 
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 );
 
435
        } else
 
436
          n = (int)( n * 1000.0 / DateTime::FRAC_SECONDS_UPPER_LIMIT );
 
437
 
 
438
        ascii::itoa_buf_type buf;
 
439
        zstring tmp( ascii::itoa( n, buf ) );
 
440
 
 
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 );
 
447
          }
 
448
        *dest += tmp;
 
449
        break;
 
450
      }
 
451
      n = (int)( n * 1000.0 / DateTime::FRAC_SECONDS_UPPER_LIMIT );
 
452
      // no break;
 
453
    default:
 
454
      append_number( n, mod, dest );
 
455
  }
 
456
}
 
457
 
 
458
static void append_string( zstring const &s, modifier const &mod,
 
459
                           zstring *dest ) {
 
460
  zstring tmp;
 
461
  switch ( mod.first.type ) {
 
462
    case modifier::name:
 
463
      utf8::to_lower( s, &tmp );
 
464
      break;
 
465
    case modifier::Name: {
 
466
      utf8::to_upper( s.substr( 0, 1 ), &tmp );
 
467
      zstring tmp2;
 
468
      utf8::to_lower( s.substr( 1 ), &tmp2 );
 
469
      tmp += tmp2;
 
470
      break;
 
471
    }
 
472
    case modifier::NAME:
 
473
      utf8::to_upper( s, &tmp );
 
474
      break;
 
475
    default:
 
476
      break;
 
477
  }
 
478
  *dest += mod.right_pad_space( &tmp );
 
479
}
 
480
 
 
481
static void append_month( unsigned mon, modifier const &mod, zstring *dest ) {
 
482
  switch ( mod.first.type ) {
 
483
    case modifier::name:
 
484
    case modifier::Name:
 
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() ) ) {
 
489
        //
 
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
 
496
        // if not.
 
497
        //
 
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 );
 
501
      }
 
502
      mod.append_if_fallback_lang( dest );
 
503
      append_string( name, mod, dest );
 
504
      break;
 
505
    }
 
506
    default:
 
507
      append_number( mon + 1, mod, dest );
 
508
  }
 
509
}
 
510
 
 
511
static void append_timezone( char component, TimeZone const &tz,
 
512
                             modifier const &mod, zstring *dest ) {
 
513
  ascii::itoa_buf_type buf;
 
514
  zstring format, tmp;
 
515
  bool has_grouping_separators;
 
516
 
 
517
  if ( mod.first.format.empty() ) {
 
518
    format = "01:01";
 
519
    has_grouping_separators = true;
 
520
  } else {
 
521
    format = mod.first.format;
 
522
    has_grouping_separators = mod.first.has_grouping_separators;
 
523
  }
 
524
 
 
525
  int hour = tz.getHours();
 
526
  int const min  = std::abs( tz.getMinutes() );
 
527
 
 
528
  switch ( mod.first.type ) {
 
529
    case modifier::NAME:
 
530
      //
 
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
 
540
      // +01:01.
 
541
      //
 
542
      if ( !min )
 
543
        switch ( hour ) {
 
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;
 
549
        }
 
550
      // TODO: use Olson timezone names
 
551
      goto fallback;
 
552
 
 
553
    case modifier::military_tz:
 
554
      //
 
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.
 
559
      //
 
560
      if ( tz.timeZoneNotSet() ) {
 
561
        //
 
562
        // Ibid: The letter J (meaning local time) is used in the case of a
 
563
        // value that does not specify a timezone offset.
 
564
        //
 
565
        tmp += 'J';
 
566
        break;
 
567
      }
 
568
      if ( hour >= -12 && hour <= 12 && !min ) {
 
569
        tmp += time::get_military_tz( hour );
 
570
        break;
 
571
      }
 
572
      //
 
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.
 
576
      //
 
577
      // no break;
 
578
 
 
579
fallback:
 
580
      format = "01:01";
 
581
      // no break;
 
582
 
 
583
    default:
 
584
      if ( component == 'z' ) {
 
585
        //
 
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.
 
591
        //
 
592
        tmp = "GMT";
 
593
      }
 
594
 
 
595
      if ( mod.second.at_type == modifier::traditional && !hour && !min ) {
 
596
        //
 
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.
 
601
        //
 
602
        tmp += 'Z';
 
603
        break;
 
604
      }
 
605
 
 
606
      if ( tz.isNegative() )
 
607
        tmp += '-', hour = std::abs( hour );
 
608
      else
 
609
        tmp += '+';
 
610
 
 
611
      if ( has_grouping_separators ) {
 
612
        //
 
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.
 
617
        //
 
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 );
 
623
 
 
624
        FOR_EACH( utf8_string<zstring const>, i, u_format ) {
 
625
          unicode::code_point const cp = *i;
 
626
          if ( unicode::is_Nd( cp ) ) {
 
627
            got_digit = true;
 
628
            if ( grouping_separators < 2 )
 
629
              ++hm_width[ grouping_separators ];
 
630
            continue;
 
631
          }
 
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 );
 
636
            }
 
637
          } else if ( grouping_separators )
 
638
            grouping_separators = 99;
 
639
          u_tmp += cp;
 
640
        }
 
641
 
 
642
        if ( hm_width[1] ) {
 
643
          zstring tmp2( utf8::itou( min, buf, mod.first.zero ) );
 
644
          tmp += utf8::left_pad( &tmp2, hm_width[1], mod.first.zero );
 
645
        }
 
646
      } else {
 
647
        utf8_string<zstring const> const u_format( format );
 
648
        utf8_string<zstring const>::size_type const u_size( u_format.size() );
 
649
 
 
650
        if ( u_size <= 2 ) {
 
651
          //
 
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.
 
659
          //
 
660
          zstring tmp2( utf8::itou( hour, buf, mod.first.zero ) );
 
661
          tmp += utf8::left_pad( &tmp2, u_size, mod.first.zero );
 
662
          if ( min ) {
 
663
            tmp2 = utf8::itou( min, buf, mod.first.zero );
 
664
            tmp += ':';
 
665
            tmp += utf8::left_pad( &tmp2, 2, mod.first.zero );
 
666
          }
 
667
          break;
 
668
        }
 
669
        if ( u_size <= 4 ) {
 
670
          //
 
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.
 
675
          //
 
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 );
 
679
          break;
 
680
        }
 
681
      } // else
 
682
  } // switch
 
683
 
 
684
append:
 
685
  *dest += tmp;
 
686
}
 
687
 
 
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 );
 
691
 
 
692
  modifier mod_copy( mod );
 
693
  if ( !mod.first.parsed )
 
694
    mod_copy.first.type = modifier::name;
 
695
 
 
696
  switch ( mod_copy.first.type ) {
 
697
    case modifier::name:
 
698
    case modifier::Name:
 
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() ) ) {
 
703
        //
 
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
 
710
        // if not.
 
711
        //
 
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 );
 
715
      }
 
716
      mod.append_if_fallback_lang( dest );
 
717
      append_string( name, mod_copy, dest );
 
718
      break;
 
719
    }
 
720
    default: {
 
721
      int const new_wday = calendar::convert_wday_to( wday, mod.cal );
 
722
      if ( mod.cal_is_fallback || new_wday == -1 ) {
 
723
        //
 
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.
 
729
        //
 
730
        ostringstream oss;
 
731
        // TODO: localize "Calendar"
 
732
        oss << "[Calendar: "
 
733
            << ( new_wday == -1 ? calendar::get_default() : mod.cal ) << ']';
 
734
        *dest += oss.str();
 
735
      } else
 
736
        wday = new_wday;
 
737
      append_number( wday, mod_copy, dest );
 
738
    }
 
739
  }
 
740
}
 
741
 
 
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 );
 
745
  if ( week == -1 ) {
 
746
    week = time::calendar::calc_week_in_year( mday, mon, year, calendar::ISO );
 
747
    ostringstream oss;
 
748
    // TODO: localize "Calendar"
 
749
    oss << "[Calendar: " << calendar::string_of[ calendar::ISO ] << ']';
 
750
    *dest += oss.str();
 
751
  }
 
752
  append_number( week, mod, dest );
 
753
}
 
754
 
 
755
static void append_year( int year, modifier const &mod, zstring *s ) {
 
756
  zstring tmp;
 
757
  append_number( year, mod, &tmp );
 
758
 
 
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 ) ) {
 
763
      //
 
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
 
770
      // 03.
 
771
      //
 
772
      u_tmp = u_tmp.substr( u_size - mod.max_width );
 
773
    }
 
774
  }
 
775
  *s += tmp;
 
776
}
 
777
 
 
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 == ',' ) {
 
784
    //
 
785
    // Assume that the ',' is the start of the width modifier (hence there is
 
786
    // neither a first nor second modifier).
 
787
    //
 
788
    return;
 
789
  }
 
790
 
 
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;
 
795
 
 
796
  if ( cp != '#' && is_grouping_separator( cp ) ) {
 
797
    //
 
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 ....
 
800
    //
 
801
    throw XQUERY_EXCEPTION(
 
802
      err::FOFD1340,
 
803
      ERROR_PARAMS(
 
804
        picture_str,
 
805
        ZED( FOFD1340_NoGroupSepAtStart_3 ),
 
806
        unicode::printable_cp( cp )
 
807
      ),
 
808
      ERROR_LOC( loc )
 
809
    );
 
810
  }
 
811
 
 
812
  //
 
813
  // Because of:
 
814
  //
 
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.
 
818
  //
 
819
  // we have to count the number of commas in order to know when we've reached
 
820
  // the last one.
 
821
  //
 
822
  int commas = 0;
 
823
  for ( zstring::const_iterator c( *i ); c != picture_str.end(); ++c )
 
824
    if ( *c == ',' )
 
825
      ++commas;
 
826
 
 
827
  unicode::code_point zero[2];
 
828
 
 
829
  if ( cp == '#' || unicode::is_Nd( cp, &zero[0] ) ) {
 
830
    bool got_grouping_separator = false;
 
831
    bool got_mandatory_digit = cp != '#';
 
832
 
 
833
    u_mod_format = *u;
 
834
    while ( ++u != u_picture_str.end() ) {
 
835
      cp = *u;
 
836
      if ( cp == '#' ) {
 
837
        if ( got_mandatory_digit ) {
 
838
          //
 
839
          // Ibid: There may be zero or more optional-digit-signs, and (if
 
840
          // present) these must precede all mandatory-digit-signs.
 
841
          //
 
842
          throw XQUERY_EXCEPTION(
 
843
            err::FOFD1340,
 
844
            ERROR_PARAMS(
 
845
              picture_str,
 
846
              ZED( FOFD1340_NoOptDigitAfterMandatory )
 
847
            ),
 
848
            ERROR_LOC( loc )
 
849
          );
 
850
        }
 
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] ) {
 
855
            //
 
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.
 
860
            //
 
861
            throw XQUERY_EXCEPTION(
 
862
              err::FOFD1340,
 
863
              ERROR_PARAMS(
 
864
                picture_str,
 
865
                ZED( FOFD1340_DigitNotSameFamily_34 ),
 
866
                unicode::printable_cp( cp ),
 
867
                unicode::printable_cp( zero[1] )
 
868
              ),
 
869
              ERROR_LOC( loc )
 
870
            );
 
871
          }
 
872
          //
 
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.
 
876
          //
 
877
          if ( !mod->min_width )
 
878
            mod->min_width = mod->max_width = 2;
 
879
          else
 
880
            mod->min_width = ++mod->max_width;
 
881
        } else
 
882
          got_mandatory_digit = true;
 
883
        got_grouping_separator = false;
 
884
      } else if ( cp == ';' || cp == ']' )
 
885
        break;
 
886
      else if ( unicode::is_space( cp ) )
 
887
        continue;
 
888
      else if ( is_grouping_separator( cp ) ) {
 
889
        if ( cp == ',' && !--commas ) {
 
890
          //
 
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.
 
894
          //
 
895
          break;
 
896
        }
 
897
        if ( got_grouping_separator ) {
 
898
          //
 
899
          // Ibid: A grouping-separator-sign must not appear ... adjacent to
 
900
          // another grouping-separator-sign.
 
901
          //
 
902
          throw XQUERY_EXCEPTION(
 
903
            err::FOFD1340,
 
904
            ERROR_PARAMS(
 
905
              picture_str,
 
906
              ZED( FOFD1340_NoAdjacentGroupSep_3 ),
 
907
              unicode::printable_cp( cp )
 
908
            ),
 
909
            ERROR_LOC( loc )
 
910
          );
 
911
        }
 
912
        got_grouping_separator = true;
 
913
        mod->first.has_grouping_separators = true;
 
914
      } else
 
915
        break;
 
916
 
 
917
      u_mod_format += cp;
 
918
    } // while
 
919
    if ( got_grouping_separator ) {
 
920
      //
 
921
      // Ibid: A grouping-separator-sign must not appear at the ... end of the
 
922
      // decimal-digit-pattern ....
 
923
      //
 
924
      throw XQUERY_EXCEPTION(
 
925
        err::FOFD1340,
 
926
        ERROR_PARAMS(
 
927
          picture_str,
 
928
          ZED( FOFD1340_NoGroupSepAtEnd_3 ),
 
929
          unicode::printable_cp( cp )
 
930
        ),
 
931
        ERROR_LOC( loc )
 
932
      );
 
933
    }
 
934
    if ( !got_mandatory_digit ) {
 
935
      //
 
936
      // Ibid: There must be at least one mandatory-digit-sign.
 
937
      //
 
938
      throw XQUERY_EXCEPTION(
 
939
        err::FOFD1340,
 
940
        ERROR_PARAMS( picture_str, ZED( FOFD1340_MustBeOneMandatoryDigit ) ),
 
941
        ERROR_LOC( loc )
 
942
      );
 
943
    }
 
944
    mod->first.zero = zero[0];
 
945
    j = u.base();
 
946
  } else {
 
947
    switch ( *j++ ) {
 
948
      case 'A':
 
949
        mod->first.type = modifier::ALPHA;
 
950
        break;
 
951
      case 'a':
 
952
        mod->first.type = modifier::alpha;
 
953
        break;
 
954
      case 'I':
 
955
        mod->first.type = modifier::ROMAN;
 
956
        break;
 
957
      case 'i':
 
958
        mod->first.type = modifier::roman;
 
959
        break;
 
960
      case 'N':
 
961
        if ( j != picture_str.end() && *j == 'n' )
 
962
          mod->first.type = modifier::Name, ++j;
 
963
        else
 
964
          mod->first.type = modifier::NAME;
 
965
        break;
 
966
      case 'n':
 
967
        mod->first.type = modifier::name;
 
968
        break;
 
969
      case 'W':
 
970
        if ( j != picture_str.end() && *j == 'w' )
 
971
          mod->first.type = modifier::Words, ++j;
 
972
        else
 
973
          mod->first.type = modifier::WORDS;
 
974
        break;
 
975
      case 'w':
 
976
        mod->first.type = modifier::words;
 
977
        break;
 
978
      case 'Z':
 
979
        mod->first.type = modifier::military_tz;
 
980
        break;
 
981
      default:
 
982
        //
 
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.
 
985
        //
 
986
        mod->first.type = modifier::arabic;
 
987
    } // switch
 
988
  }
 
989
  mod->first.parsed = true;
 
990
}
 
991
 
 
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() )
 
998
    return;
 
999
  switch ( *j ) {
 
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;
 
1004
    default : return;
 
1005
  }
 
1006
  if ( ++j == picture_str.end() )
 
1007
    return;
 
1008
  if ( *j == '(' ) {
 
1009
    while ( true ) {
 
1010
      if ( ++j == picture_str.end() )
 
1011
        throw XQUERY_EXCEPTION(
 
1012
          err::FOFD1340,
 
1013
          ERROR_PARAMS( picture_str, ZED( CharExpected_3 ), ')' ),
 
1014
          ERROR_LOC( loc )
 
1015
        );
 
1016
      if ( *j == ')' )
 
1017
        break;
 
1018
      mod->second.co_string += *j;
 
1019
    } 
 
1020
    ++j;
 
1021
  }
 
1022
}
 
1023
 
 
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;
 
1028
 
 
1029
  ascii::skip_whitespace( picture_str, &j );
 
1030
  if ( j == picture_str.end() || (*j != ',' && *j != ';') )
 
1031
    return;
 
1032
  ascii::skip_whitespace( picture_str, &++j );
 
1033
  if ( j == picture_str.end() )
 
1034
    goto bad_width_modifier;
 
1035
  if ( *j == '*' ) {
 
1036
    mod->min_width = 0;
 
1037
    ++j;
 
1038
  } else {
 
1039
    try {
 
1040
      mod->min_width = static_cast<modifier::width_type>(
 
1041
        ztd::atoull( j, picture_str.end(), &j )
 
1042
      );
 
1043
    }
 
1044
    catch ( std::exception const& ) {
 
1045
      goto bad_width_modifier;
 
1046
    }
 
1047
  }
 
1048
 
 
1049
  mod->max_width = 0;
 
1050
 
 
1051
  ascii::skip_whitespace( picture_str, &j );
 
1052
  if ( j == picture_str.end() || *j != '-' )
 
1053
    return;
 
1054
  ascii::skip_whitespace( picture_str, &++j );
 
1055
  if ( j == picture_str.end() )
 
1056
    goto bad_width_modifier;
 
1057
  if ( *j == '*' )
 
1058
    ++j;
 
1059
  else {
 
1060
    try {
 
1061
      mod->max_width = static_cast<modifier::width_type>(
 
1062
        ztd::atoull( j, picture_str.end(), &j )
 
1063
      );
 
1064
    }
 
1065
    catch ( std::exception const& ) {
 
1066
      goto bad_width_modifier;
 
1067
    }
 
1068
  }
 
1069
 
 
1070
  return;
 
1071
 
 
1072
bad_width_modifier:
 
1073
  throw XQUERY_EXCEPTION(
 
1074
    err::FOFD1340,
 
1075
    ERROR_PARAMS( picture_str, ZED( FOFD1340_BadWidthModifier ) ),
 
1076
    ERROR_LOC( loc )
 
1077
  );
 
1078
}
 
1079
 
 
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;
 
1097
  }
 
1098
}
 
1099
 
 
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;
 
1109
  store::Item_t item;
 
1110
  PlanIteratorState *state;
 
1111
 
 
1112
  DEFAULT_STACK_INIT( PlanIteratorState, state, planState);
 
1113
 
 
1114
  if ( !consumeNext( item, theChildren[0].getp(), planState ) ) {
 
1115
    // Got the empty sequence -- return same
 
1116
    STACK_PUSH( false, state );
 
1117
  } else {
 
1118
    dateTime = item->getDateTimeValue();
 
1119
    consumeNext( item, theChildren[1].getp(), planState );
 
1120
    item->getStringValue2( picture_str );
 
1121
 
 
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;
 
1128
      }
 
1129
 
 
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 );
 
1134
      if ( !cal )
 
1135
        cal_is_fallback = true;
 
1136
 
 
1137
      consumeNext( item, theChildren[4].getp(), planState );
 
1138
      item->getStringValue2( item_str );
 
1139
      // TODO: do something with place
 
1140
    }
 
1141
 
 
1142
    if ( !cal ) {
 
1143
      //
 
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.
 
1147
      //
 
1148
      cal = planState.theLocalDynCtx->get_calendar();
 
1149
    }
 
1150
 
 
1151
    if ( !lang ) {
 
1152
      //
 
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.
 
1157
      //
 
1158
      planState.theLocalDynCtx->get_locale( &lang, &country );
 
1159
    }
 
1160
 
 
1161
    char component;
 
1162
    in_variable_marker = false;
 
1163
 
 
1164
    FOR_EACH( zstring, i, picture_str ) {
 
1165
      if ( !in_variable_marker ) {
 
1166
        switch ( *i ) {
 
1167
          case '[':
 
1168
            if ( ztd::peek( picture_str, i ) == '[' )
 
1169
              ++i;
 
1170
            else {
 
1171
              component = 0;
 
1172
              in_variable_marker = true;
 
1173
              continue;
 
1174
            }
 
1175
            break;
 
1176
          case ']':
 
1177
            if ( ztd::peek( picture_str, i ) == ']' )
 
1178
              ++i;
 
1179
            break;
 
1180
        }
 
1181
        result_str += *i;
 
1182
        continue;
 
1183
      }
 
1184
 
 
1185
      if ( ascii::is_space( *i ) )
 
1186
        continue;                       // ignore all whitespace
 
1187
 
 
1188
      switch ( *i ) {
 
1189
        case ']':
 
1190
          if ( !component )
 
1191
            throw XQUERY_EXCEPTION(
 
1192
              err::FOFD1340,
 
1193
              ERROR_PARAMS( picture_str, ZED( FOFD1340_NoComponent ) ),
 
1194
              ERROR_LOC( loc )
 
1195
            );
 
1196
          component = 0;
 
1197
          in_variable_marker = false;
 
1198
          continue;
 
1199
        case 'C':
 
1200
        case 'D':
 
1201
        case 'd':
 
1202
        case 'E':
 
1203
        case 'F':
 
1204
        case 'f':
 
1205
        case 'H':
 
1206
        case 'h':
 
1207
        case 'M':
 
1208
        case 'm':
 
1209
        case 'P':
 
1210
        case 's':
 
1211
        case 'W':
 
1212
        case 'w':
 
1213
        case 'Y':
 
1214
        case 'Z':
 
1215
        case 'z':
 
1216
#if 0
 
1217
          if ( component )
 
1218
            throw XQUERY_EXCEPTION(
 
1219
              err::FOFD1340,
 
1220
              ERROR_PARAMS(
 
1221
                picture_str, ZED( FOFD1340_MultipleComponent_3 ), *i
 
1222
              ),
 
1223
              ERROR_LOC( loc )
 
1224
            );
 
1225
#endif
 
1226
          component = *i;
 
1227
          break;
 
1228
        default:
 
1229
          throw XQUERY_EXCEPTION(
 
1230
            err::FOFD1340,
 
1231
            ERROR_PARAMS( picture_str, ZED( FOFD1340_BadComponent_3 ), *i ),
 
1232
            ERROR_LOC( loc )
 
1233
          );
 
1234
      } // switch
 
1235
      if ( ++i == picture_str.end() )
 
1236
        goto eos;
 
1237
 
 
1238
      modifier mod;
 
1239
      mod.lang = lang;
 
1240
      mod.lang_is_fallback = lang_is_fallback;
 
1241
      mod.country = country;
 
1242
      mod.cal = cal;
 
1243
      mod.cal_is_fallback = cal_is_fallback;
 
1244
 
 
1245
      if ( *i != ']' ) {
 
1246
        parse_first_modifier( picture_str, &i, &mod, loc );
 
1247
        if ( i == picture_str.end() )
 
1248
          goto eos;
 
1249
        if ( *i != ']' ) {
 
1250
          parse_second_modifier( picture_str, &i, &mod, loc );
 
1251
          if ( i == picture_str.end() )
 
1252
            goto eos;
 
1253
          parse_width_modifier( picture_str, &i, &mod, loc );
 
1254
          if ( i == picture_str.end() )
 
1255
            goto eos;
 
1256
        }
 
1257
      }
 
1258
      if ( *i == ']' )
 
1259
        --i;
 
1260
 
 
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 )
 
1265
        );
 
1266
 
 
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 );
 
1273
          break;
 
1274
        }
 
1275
        case 'D':
 
1276
          append_number( dateTime.getDay(), mod, &result_str );
 
1277
          break;
 
1278
        case 'd':
 
1279
          append_number( dateTime.getDayOfYear(), mod, &result_str );
 
1280
          break;
 
1281
        case 'E': { // era
 
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 );
 
1288
          break;
 
1289
        }
 
1290
        case 'F': {
 
1291
          modifier mod_copy( mod );
 
1292
          if ( !mod.first.parsed )
 
1293
            mod_copy.first.type = modifier::name;
 
1294
          append_weekday(
 
1295
            dateTime.getDay(), dateTime.getMonth() - 1, dateTime.getYear(),
 
1296
            mod_copy, &result_str
 
1297
          );
 
1298
          break;
 
1299
        }
 
1300
        case 'f':
 
1301
          append_fractional_seconds(
 
1302
            dateTime.getFractionalSeconds(), mod, &result_str
 
1303
          );
 
1304
          break;
 
1305
        case 'H': // hour (24 hours)
 
1306
          append_number( dateTime.getHours(), mod, &result_str );
 
1307
          break;
 
1308
        case 'h': // hour (12 hours)
 
1309
          // Convert hour from:  0 1 ... 12 13 ... 23
 
1310
          //                to: 12 1 ... 12  1 ... 11
 
1311
          append_number(
 
1312
            1 + (11 + dateTime.getHours()) % 12, mod, &result_str
 
1313
          );
 
1314
          break;
 
1315
        case 'M':
 
1316
          append_month( dateTime.getMonth() - 1, mod, &result_str );
 
1317
          break;
 
1318
        case 'm': {
 
1319
          modifier mod_copy( mod );
 
1320
          mod_copy.set_default_width( 2 );
 
1321
          append_number( dateTime.getMinutes(), mod_copy, &result_str );
 
1322
          break;
 
1323
        }
 
1324
        case 'P': {
 
1325
          modifier mod_copy( mod );
 
1326
          if ( !mod.first.parsed )
 
1327
            mod_copy.first.type = modifier::name;
 
1328
          append_string(
 
1329
            locale::get_time_ampm( dateTime.getHours() >= 12, lang, country ),
 
1330
            mod_copy, &result_str
 
1331
          );
 
1332
          break;
 
1333
        }
 
1334
        case 's': {
 
1335
          modifier mod_copy( mod );
 
1336
          mod_copy.set_default_width( 2 );
 
1337
          append_number( dateTime.getIntSeconds(), mod_copy, &result_str );
 
1338
          break;
 
1339
        }
 
1340
        case 'W':
 
1341
          append_week_in_year(
 
1342
            dateTime.getDay(), dateTime.getMonth() - 1, dateTime.getYear(),
 
1343
            mod, &result_str
 
1344
          );
 
1345
          break;
 
1346
        case 'w':
 
1347
          append_number( dateTime.getWeekInMonth(), mod, &result_str );
 
1348
          break;
 
1349
        case 'Y':
 
1350
          append_year( std::abs( dateTime.getYear() ), mod, &result_str );
 
1351
          break;
 
1352
        case 'Z':
 
1353
        case 'z':
 
1354
          append_timezone(
 
1355
            component, dateTime.getTimezone(), mod, &result_str
 
1356
          );
 
1357
          break;
 
1358
      } // switch
 
1359
    } // for
 
1360
 
 
1361
    if ( in_variable_marker )
 
1362
eos:  throw XQUERY_EXCEPTION(
 
1363
        err::FOFD1340,
 
1364
        ERROR_PARAMS( picture_str, ZED( CharExpected_3 ), ']' ),
 
1365
        ERROR_LOC( loc )
 
1366
      );
 
1367
 
 
1368
    STACK_PUSH( GENV_ITEMFACTORY->createString( result, result_str ), state );
 
1369
  }
 
1370
 
 
1371
  STACK_END( state );
 
1372
}
 
1373
 
 
1374
///////////////////////////////////////////////////////////////////////////////
 
1375
 
 
1376
} // namespace zorba
 
1377
/* vim:set et sw=2 ts=2: */