1 /++
2 FGHJ Representation
3 
4 Copyright: Tamedia Digital, 2016
5 
6 Authors: Ilya Yaroshenko
7 
8 License: MIT
9 
10 Macros:
11 SUBMODULE = $(LINK2 fghj_$1.html, fghj.$1)
12 SUBREF = $(LINK2 fghj_$1.html#.$2, $(TT $2))$(NBSP)
13 T2=$(TR $(TDNW $(LREF $1)) $(TD $+))
14 T4=$(TR $(TDNW $(LREF $1)) $(TD $2) $(TD $3) $(TD $4))
15 +/
16 module fghj.fghj;
17 
18 import std.exception;
19 import std.range.primitives;
20 import std.typecons;
21 import std.traits;
22 
23 import fghj.jsonbuffer;
24 import fghj.jsonparser: assumePure;
25 
26 version(X86_64)
27     version = X86_Any;
28 else
29 version(X86)
30     version = X86_Any;
31 
32 version (D_Exceptions)
33 {
34     import mir.serde: SerdeException;
35     /++
36     Serde Exception
37     +/
38     class FghjSerdeException : SerdeException
39     {
40         /// zero based faulty location
41         size_t location;
42 
43         ///
44         this(
45             string msg,
46             size_t location,
47             string file = __FILE__,
48             size_t line = __LINE__,
49             ) pure nothrow @nogc @safe 
50         {
51             this.location = location;
52             super(msg, file, line);
53         }
54 
55         ///
56         this(
57             string msg,
58             string file = __FILE__,
59             size_t line = __LINE__,
60             Throwable next = null) pure nothrow @nogc @safe 
61         {
62             super(msg, file, line, next);
63         }
64 
65         ///
66         this(
67             string msg,
68             Throwable next,
69             string file = __FILE__,
70             size_t line = __LINE__,
71             ) pure nothrow @nogc @safe 
72         {
73             this(msg, file, line, next);
74         }
75 
76         override FghjSerdeException toMutable() @trusted pure nothrow @nogc const
77         {
78             return cast() this;
79         }
80 
81         alias toMutable this;
82     }
83 }
84 
85 deprecated("use mir.serde: SerdeException instead")
86 alias FghjException = SerdeException;
87 
88 ///
89 class InvalidFghjException: SerdeException
90 {
91     ///
92     this(
93         uint kind,
94         string file = __FILE__,
95         size_t line = __LINE__,
96         Throwable next = null) pure nothrow @safe 
97     {
98         import mir.format: text;
99         super(text("FGHJ values is invalid for kind = ", kind), file, line, next);
100     }
101 
102     ///
103     this(
104         uint kind,
105         Throwable next,
106         string file = __FILE__,
107         size_t line = __LINE__,
108         ) pure nothrow @safe 
109     {
110         this(kind, file, line, next);
111     }
112 }
113 
114 private void enforceValidFghj(
115         bool condition,
116         uint kind,
117         string file = __FILE__,
118         size_t line = __LINE__) @safe pure
119 {
120     if(!condition)
121         throw new InvalidFghjException(kind, file, line);
122 }
123 
124 ///
125 class EmptyFghjException: SerdeException
126 {
127     ///
128     this(
129         string msg = "FGHJ value is empty",
130         string file = __FILE__,
131         size_t line = __LINE__,
132         Throwable next = null) pure nothrow @nogc @safe 
133     {
134         super(msg, file, line, next);
135     }
136 }
137 
138 /++
139 The structure for FGHJ manipulation.
140 +/
141 struct Fghj
142 {
143     ///
144     enum Kind : ubyte
145     {
146         ///
147         null_  = 0x00,
148         ///
149         true_  = 0x01,
150         ///
151         false_ = 0x02,
152         ///
153         number = 0x03,
154         ///
155         string = 0x05,
156         ///
157         array  = 0x09,
158         ///
159         object = 0x0A,
160     }
161 
162     /// Returns FGHJ Kind
163     ubyte kind() const pure @safe @nogc
164     {
165         if (!data.length)
166         {
167             static immutable exc = new EmptyFghjException;
168             throw exc;
169         }
170         return data[0];
171     }
172 
173     /++
174     Plain FGHJ data.
175     +/
176     ubyte[] data;
177 
178     /// Creates FGHJ using already allocated data
179     this(ubyte[] data) pure @safe nothrow @nogc
180     {
181         this.data = data;
182     }
183 
184     /// Creates FGHJ from a string
185     this(in char[] str) pure @safe
186     {
187         data = new ubyte[str.length + 5];
188         data[0] = Kind..string;
189         length4 = str.length;
190         data[5 .. $] = cast(const(ubyte)[])str;
191     }
192 
193     ///
194     unittest
195     {
196         assert(Fghj("string") == "string");
197         assert(Fghj("string") != "String");
198     }
199 
200     // \uXXXX character support
201     unittest
202     {
203         import mir.conv: to;
204         import fghj.jsonparser;
205         assert(Fghj("begin\u000bend").to!string == `"begin\u000Bend"`);
206         assert("begin\u000bend" == cast(string) `"begin\u000Bend"`.parseJson, to!string(cast(ubyte[]) cast(string)( `"begin\u000Bend"`.parseJson)));
207     }
208 
209     /// Sets deleted bit on
210     void remove() pure @safe nothrow @nogc
211     {
212         if(data.length)
213             data[0] |= 0x80;
214     }
215 
216     ///
217     unittest
218     {
219         import mir.conv: to;
220         import fghj.jsonparser;
221         auto fghjData = `{"foo":"bar","inner":{"a":true,"b":false,"c":"32323","d":null,"e":{}}}`.parseJson;
222         fghjData["inner", "d"].remove;
223         assert(fghjData.to!string == `{"foo":"bar","inner":{"a":true,"b":false,"c":"32323","e":{}}}`);
224     }
225 
226     ///
227     void toString(Dg)(scope Dg sink) const
228     {
229         scope buffer = JsonBuffer!Dg(sink);
230         toStringImpl(buffer);
231         buffer.flush;
232     }
233 
234     /+
235     Internal recursive toString implementation.
236     Params:
237         sink = output range that accepts `char`, `in char[]` and compile time string `(string str)()`
238     +/
239     private void toStringImpl(Dg)(ref JsonBuffer!Dg sink) const
240     {
241         if (!data.length)
242         {
243             static immutable exc = new EmptyFghjException("Data buffer is empty");
244             throw exc;
245         }
246         auto t = data[0];
247         switch(t)
248         {
249             case Kind.null_:
250                 enforceValidFghj(data.length == 1, t);
251                 sink.put!"null";
252                 break;
253             case Kind.true_:
254                 enforceValidFghj(data.length == 1, t);
255                 sink.put!"true";
256                 break;
257             case Kind.false_:
258                 enforceValidFghj(data.length == 1, t);
259                 sink.put!"false";
260                 break;
261             case Kind.number:
262                 enforceValidFghj(data.length > 1, t);
263                 size_t length = data[1];
264                 enforceValidFghj(data.length == length + 2, t);
265                 sink.putSmallEscaped(cast(const(char)[]) data[2 .. $]);
266                 break;
267             case Kind..string:
268                 enforceValidFghj(data.length >= 5, Kind.object);
269                 enforceValidFghj(data.length == length4 + 5, t);
270                 sink.put('"');
271                 sink.put(cast(const(char)[]) data[5 .. $]);
272                 sink.put('"');
273                 break;
274             case Kind.array:
275                 auto elems = Fghj(cast(ubyte[])data).byElement;
276                 if(elems.empty)
277                 {
278                     sink.put!"[]";
279                     break;
280                 }
281                 sink.put('[');
282                 elems.front.toStringImpl(sink);
283                 elems.popFront;
284                 foreach(e; elems)
285                 {
286                     sink.put(',');
287                     e.toStringImpl(sink);
288                 }
289                 sink.put(']');
290                 break;
291             case Kind.object:
292                 auto pairs = Fghj(cast(ubyte[])data).byKeyValue;
293                 if(pairs.empty)
294                 {
295                     sink.put!"{}";
296                     break;
297                 }
298                 sink.put!"{\"";
299                 sink.put(pairs.front.key);
300                 sink.put!"\":";
301                 pairs.front.value.toStringImpl(sink);
302                 pairs.popFront;
303                 foreach(e; pairs)
304                 {
305                     sink.put!",\"";
306                     sink.put(e.key);
307                     sink.put!"\":";
308                     e.value.toStringImpl(sink);
309                 }
310                 sink.put('}');
311                 break;
312             default:
313                 enforceValidFghj(0, t);
314         }
315     }
316 
317     ///
318     unittest
319     {
320         import mir.conv: to;
321         import fghj.jsonparser;
322         auto text = `{"foo":"bar","inner":{"a":true,"b":false,"c":"32323","d":null,"e":{}}}`;
323         const fghjData = text.parseJson;
324         assert(fghjData.to!string == text);
325     }
326 
327     /++
328     `==` operator overloads for `null`
329     +/
330     bool opEquals(in Fghj rhs) const @safe pure nothrow @nogc
331     {
332         return data == rhs.data;
333     }
334 
335     ///
336     unittest
337     {
338         import fghj.jsonparser;
339         auto fghjData = `null`.parseJson;
340         assert(fghjData == fghjData);
341     }
342 
343     /++
344     `==` operator overloads for `null`
345     +/
346     bool opEquals(typeof(null)) const pure @safe nothrow
347     {
348         return data.length == 1 && data[0] == 0;
349     }
350 
351     ///
352     unittest
353     {
354         import fghj.jsonparser;
355         auto fghjData = `null`.parseJson;
356         assert(fghjData == null);
357     }
358 
359     /++
360     `==` operator overloads for `bool`
361     +/
362     bool opEquals(bool boolean) const pure @safe nothrow
363     {
364         return data.length == 1 && (data[0] == Kind.true_ && boolean || data[0] == Kind.false_ && !boolean);
365     }
366 
367     ///
368     unittest
369     {
370         import fghj.jsonparser;
371         auto fghjData = `true`.parseJson;
372         assert(fghjData == true);
373         assert(fghjData != false);
374     }
375 
376     /++
377     `==` operator overloads for `string`
378     +/
379     bool opEquals(in char[] str) const pure @trusted nothrow
380     {
381         return data.length >= 5 && data[0] == Kind..string && data[5 .. 5 + length4] == cast(const(ubyte)[]) str;
382     }
383 
384     ///
385     unittest
386     {
387         import fghj.jsonparser;
388         auto fghjData = `"str"`.parseJson;
389         assert(fghjData == "str");
390         assert(fghjData != "stR");
391     }
392 
393     /++
394     Returns:
395         input range composed of elements of an array.
396     +/
397     auto byElement() pure
398     {
399         static struct Range
400         {
401             private ubyte[] _data;
402             private Fghj _front;
403 
404             auto save()() pure @property
405             {
406                 return this;
407             }
408 
409             void popFront() pure
410             {
411                 while(!_data.empty)
412                 {
413                     uint t = cast(ubyte) _data.front;
414                     switch(t)
415                     {
416                         case Kind.null_:
417                         case Kind.true_:
418                         case Kind.false_:
419                             _front = Fghj(_data[0 .. 1]);
420                             _data.popFront;
421                             return;
422                         case Kind.number:
423                             enforceValidFghj(_data.length >= 2, t);
424                             size_t len = _data[1] + 2;
425                             enforceValidFghj(_data.length >= len, t);
426                             _front = Fghj(_data[0 .. len]);
427                             _data = _data[len .. $];
428                             return;
429                         case Kind..string:
430                         case Kind.array:
431                         case Kind.object:
432                             enforceValidFghj(_data.length >= 5, t);
433                             size_t len = Fghj(_data).length4 + 5;
434                             enforceValidFghj(_data.length >= len, t);
435                             _front = Fghj(_data[0 .. len]);
436                             _data = _data[len .. $];
437                             return;
438                         case 0x80 | Kind.null_:
439                         case 0x80 | Kind.true_:
440                         case 0x80 | Kind.false_:
441                             _data.popFront;
442                             continue;
443                         case 0x80 | Kind.number:
444                             enforceValidFghj(_data.length >= 2, t);
445                             _data.popFrontExactly(_data[1] + 2);
446                             continue;
447                         case 0x80 | Kind..string:
448                         case 0x80 | Kind.array:
449                         case 0x80 | Kind.object:
450                             enforceValidFghj(_data.length >= 5, t);
451                             size_t len = Fghj(_data).length4 + 5;
452                             _data.popFrontExactly(len);
453                             continue;
454                         default:
455                             enforceValidFghj(0, t);
456                     }
457                 }
458                 _front = Fghj.init;
459             }
460 
461             auto front() pure @property
462             {
463                 assert(!empty);
464                 return _front;
465             }
466 
467             bool empty() pure @property
468             {
469                 return _front.data.length == 0;
470             }
471         }
472         if(data.empty || data[0] != Kind.array)
473             return Range.init;
474         enforceValidFghj(data.length >= 5, Kind.array);
475         enforceValidFghj(length4 == data.length - 5, Kind.array);
476         auto ret = Range(data[5 .. $]);
477         if(ret._data.length)
478             ret.popFront;
479         return ret;
480     }
481 
482     /++
483     Returns:
484         Input range composed of key-value pairs of an object.
485         Elements are type of `Tuple!(const(char)[], "key", Fghj, "value")`.
486     +/
487     auto byKeyValue() pure
488     {
489         static struct Range
490         {
491             private ubyte[] _data;
492             private Tuple!(const(char)[], "key", Fghj, "value") _front;
493 
494             auto save() pure @property
495             {
496                 return this;
497             }
498 
499             void popFront() pure
500             {
501                 while(!_data.empty)
502                 {
503                     enforceValidFghj(_data.length > 1, Kind.object);
504                     size_t l = cast(ubyte) _data[0];
505                     _data.popFront;
506                     enforceValidFghj(_data.length >= l, Kind.object);
507                     _front.key = cast(const(char)[])_data[0 .. l];
508                     _data.popFrontExactly(l);
509                     uint t = cast(ubyte) _data.front;
510                     switch(t)
511                     {
512                         case Kind.null_:
513                         case Kind.true_:
514                         case Kind.false_:
515                             _front.value = Fghj(_data[0 .. 1]);
516                             _data.popFront;
517                             return;
518                         case Kind.number:
519                             enforceValidFghj(_data.length >= 2, t);
520                             size_t len = _data[1] + 2;
521                             enforceValidFghj(_data.length >= len, t);
522                             _front.value = Fghj(_data[0 .. len]);
523                             _data = _data[len .. $];
524                             return;
525                         case Kind..string:
526                         case Kind.array:
527                         case Kind.object:
528                             enforceValidFghj(_data.length >= 5, t);
529                             size_t len = Fghj(_data).length4 + 5;
530                             enforceValidFghj(_data.length >= len, t);
531                             _front.value = Fghj(_data[0 .. len]);
532                             _data = _data[len .. $];
533                             return;
534                         case 0x80 | Kind.null_:
535                         case 0x80 | Kind.true_:
536                         case 0x80 | Kind.false_:
537                             _data.popFront;
538                             continue;
539                         case 0x80 | Kind.number:
540                             enforceValidFghj(_data.length >= 2, t);
541                             _data.popFrontExactly(_data[1] + 2);
542                             continue;
543                         case 0x80 | Kind..string:
544                         case 0x80 | Kind.array:
545                         case 0x80 | Kind.object:
546                             enforceValidFghj(_data.length >= 5, t);
547                             size_t len = Fghj(_data).length4 + 5;
548                             _data.popFrontExactly(len);
549                             continue;
550                         default:
551                             enforceValidFghj(0, t);
552                     }
553                 }
554                 _front = _front.init;
555             }
556 
557             auto front() pure @property
558             {
559                 assert(!empty);
560                 return _front;
561             }
562 
563             bool empty() pure @property
564             {
565                 return _front.value.data.length == 0;
566             }
567         }
568         if(data.empty || data[0] != Kind.object)
569             return Range.init;
570         enforceValidFghj(data.length >= 5, Kind.object);
571         enforceValidFghj(length4 == data.length - 5, Kind.object);
572         auto ret = Range(data[5 .. $]);
573         if(ret._data.length)
574             ret.popFront;
575         return ret;
576     }
577 
578     /// returns 4-byte length
579     private size_t length4() const @property pure nothrow @nogc @trusted
580     {
581         assert(data.length >= 5);
582         version(X86_Any)
583         {
584             return (cast(uint*)(data.ptr + 1))[0];
585         }
586         else
587         {
588             align(4) auto ret = *cast(ubyte[4]*)(data.ptr + 1);
589             return (cast(uint[1])ret)[0];
590         }
591     }
592 
593     /// ditto
594     private void length4(size_t len) const @property pure nothrow @nogc @trusted
595     {
596         assert(data.length >= 5);
597         assert(len <= uint.max);
598         version(X86_Any)
599         {
600             *(cast(uint*)(data.ptr + 1)) = cast(uint) len;
601         }
602         else
603         {
604             *(cast(ubyte[4]*)(data.ptr + 1)) = cast(ubyte[4]) cast(uint[1]) [cast(uint) len];
605         }
606     }
607 
608     /++
609     Searches for a value recursively in an FGHJ object.
610 
611     Params:
612         keys = list of keys keys
613     Returns
614         FGHJ value if it was found (first win) or FGHJ with empty plain data.
615     +/
616     Fghj opIndex(in char[][] keys...) pure
617     {
618         auto fghj = this;
619         if(fghj.data.empty)
620             return Fghj.init;
621         L: foreach(key; keys)
622         {
623             if(fghj.data[0] != Fghj.Kind.object)
624                 return Fghj.init;
625             foreach(e; fghj.byKeyValue)
626             {
627                 if(e.key == key)
628                 {
629                     fghj = e.value;
630                     continue L;
631                 }
632             }
633             return Fghj.init;
634         }
635         return fghj;
636     }
637 
638     ///
639     unittest
640     {
641         import fghj.jsonparser;
642         auto fghjData = `{"foo":"bar","inner":{"a":true,"b":false,"c":"32323","d":null,"e":{}}}`.parseJson;
643         assert(fghjData["inner", "a"] == true);
644         assert(fghjData["inner", "b"] == false);
645         assert(fghjData["inner", "c"] == "32323");
646         assert(fghjData["inner", "d"] == null);
647         assert(fghjData["no", "such", "keys"] == Fghj.init);
648     }
649 
650     /++
651     Params:
652         def = default value. It is used when FGHJ value equals `Fghj.init`.
653     Returns:
654         `cast(T) this` if `this != Fghj.init` and `def` otherwise.
655     +/
656     T get(T)(T def)
657     {
658         if(data.length)
659         {
660             return cast(T) this;
661         }
662         return def;
663     }
664 
665     ///
666     unittest
667     {
668         import fghj.jsonparser;
669         auto fghjData = `{"foo":"bar","inner":{"a":true,"b":false,"c":"32323","d":null,"e":{}}}`.parseJson;
670         assert(fghjData["inner", "a"].get(false) == true);
671         assert(fghjData["inner", "b"].get(true) == false);
672         assert(fghjData["inner", "c"].get(100) == 32323);
673         assert(fghjData["no", "such", "keys"].get(100) == 100);
674     }
675 
676     /++
677     `cast` operator overloading.
678     +/
679     T opCast(T)()
680     {
681         import std.datetime: SysTime, DateTime, usecs, UTC;
682         import std.traits: isNumeric;
683         import mir.conv: to;
684         import std.conv: ConvException;
685         import std.format: format;
686         import std.math: trunc;
687         import fghj.serialization;
688         auto k = kind;
689         with(Kind) switch(kind)
690         {
691             case null_ :
692                 static if (isNumeric!T
693                         || is(T == interface)
694                         || is(T == class)
695                         || is(T == E[], E)
696                         || is(T == E[K], E, K)
697                         || is(T == bool))
698                     return T.init;
699                 else goto default;
700             case true_ :
701                 static if(__traits(compiles, true.to!T))
702                     return true.to!T;
703                 else goto default;
704             case false_:
705                 static if(__traits(compiles, false.to!T))
706                     return false.to!T;
707                 else goto default;
708             case number:
709             {
710                 auto str = cast(const(char)[]) data[2 .. $];
711                 static if(is(T == bool))
712                     return assumePure(() => str.to!double)() != 0;
713                 else
714                 static if(is(T == SysTime) || is(T == DateTime))
715                 {
716                     auto unixTime = assumePure(() => str.to!real)();
717                     auto secsR = assumePure(() => unixTime.trunc)();
718                     auto rem = unixTime - secsR;
719                     auto st = SysTime.fromUnixTime(cast(long)(secsR), UTC());
720                     assumePure((ref SysTime st) => st.fracSecs = usecs(cast(long)(rem * 1_000_000)))(st);
721                     return assumePure(() => st.to!T)();
722                 }
723                 else
724                 static if(__traits(compiles, assumePure(() => str.to!T)()))
725                     return assumePure(() => str.to!T)();
726                 else goto default;
727             }
728             case string:
729             {
730                 auto str = cast(const(char)[]) data[5 .. $];
731                 static if(is(T == bool))
732                     return str != "0" && str != "false" && str != "";
733                 else
734                 static if(__traits(compiles, str.to!T))
735                     return str.to!T;
736                 else goto default;
737             }
738             static if (isAggregateType!T || isArray!T)
739             {
740             case array :
741             case object:
742                 static if(__traits(compiles, {T t = deserialize!T(this);}))
743                     return deserialize!T(this);
744                 else goto default;
745             }
746             default:
747                 throw new ConvException(format("Cannot convert kind %s(\\x%02X) to %s", cast(Kind) k, k, T.stringof));
748         }
749     }
750 
751     /// null
752     unittest
753     {
754         import std.math;
755         import fghj.serialization;
756         auto null_ = serializeToFghj(null);
757         interface I {}
758         class C {}
759         assert(cast(uint[]) null_ is null);
760         assert(cast(uint[uint]) null_ is null);
761         assert(cast(I) null_ is null);
762         assert(cast(C) null_ is null);
763         assert(isNaN(cast(double) null_));
764         assert(! cast(bool) null_);
765     }
766 
767     /// boolean
768     unittest
769     {
770         import std.math;
771         import fghj.serialization;
772         auto true_ = serializeToFghj(true);
773         auto false_ = serializeToFghj(false);
774         static struct C {
775             this(bool){}
776         }
777         auto a = cast(C) true_;
778         auto b = cast(C) false_;
779         assert(cast(bool) true_ == true);
780         assert(cast(bool) false_ == false);
781         assert(cast(uint) true_ == 1);
782         assert(cast(uint) false_ == 0);
783         assert(cast(double) true_ == 1);
784         assert(cast(double) false_ == 0);
785     }
786 
787     /// numbers
788     unittest
789     {
790         import std.bigint;
791         import fghj.serialization;
792         auto number = serializeToFghj(1234);
793         auto zero = serializeToFghj(0);
794         static struct C
795         {
796             this(in char[] numberString)
797             {
798                 assert(numberString == "1234");
799             }
800         }
801         auto a = cast(C) number;
802         assert(cast(bool) number == true);
803         assert(cast(bool) zero == false);
804         assert(cast(uint) number == 1234);
805         assert(cast(double) number == 1234);
806         assert(cast(BigInt) number == 1234);
807         assert(cast(uint) zero == 0);
808         assert(cast(double) zero == 0);
809         assert(cast(BigInt) zero == 0);
810     }
811 
812     /// string
813     unittest
814     {
815         import std.bigint;
816         import fghj.serialization;
817         auto number = serializeToFghj("1234");
818         auto false_ = serializeToFghj("false");
819         auto bar = serializeToFghj("bar");
820         auto zero = serializeToFghj("0");
821         static struct C
822         {
823             this(in char[] str)
824             {
825                 assert(str == "1234");
826             }
827         }
828         auto a = cast(C) number;
829         assert(cast(string) number == "1234");
830         assert(cast(bool) number == true);
831         assert(cast(bool) bar == true);
832         assert(cast(bool) zero == false);
833         assert(cast(bool) false_ == false);
834         assert(cast(uint) number == 1234);
835         assert(cast(double) number == 1234);
836         assert(cast(BigInt) number == 1234);
837         assert(cast(uint) zero == 0);
838         assert(cast(double) zero == 0);
839         assert(cast(BigInt) zero == 0);
840     }
841 
842     /++
843     For FGHJ arrays and objects `cast(T)` just returns `this.deserialize!T`.
844     +/
845     unittest
846     {
847         import std.bigint;
848         import fghj.serialization;
849         assert(cast(int[]) serializeToFghj([100, 20]) == [100, 20]);
850     }
851 
852     /// UNIX Time
853     unittest
854     {
855         import std.datetime;
856         import fghj.serialization;
857 
858         auto num = serializeToFghj(0.123456789); // rounding up to usecs
859         assert(cast(DateTime) num == DateTime(1970, 1, 1));
860         assert(cast(SysTime) num == SysTime(DateTime(1970, 1, 1), usecs(123456), UTC())); // UTC time zone is used.
861     }
862 }