Skip to content

Commit 4e877fe

Browse files
committed
Timestamp: local time support
1 parent 9a61cd6 commit 4e877fe

File tree

1 file changed

+86
-72
lines changed

1 file changed

+86
-72
lines changed

source/mir/timestamp.d

Lines changed: 86 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,9 @@ version(D_Exceptions)
3737
/++
3838
Timestamp
3939
40-
Note: The component values in the binary encoding are always in UTC, while components in the text encoding are in the local time!
41-
This means that transcoding requires a conversion between UTC and local time.
40+
Note: The component values in the binary encoding are always in UTC or local time with unknown offset,
41+
while components in the text encoding are in a some timezone with known offset.
42+
This means that transcoding requires a conversion between UTC and a timezone.
4243
4344
`Timestamp` precision is up to picosecond (second/10^12).
4445
+/
@@ -81,45 +82,52 @@ struct Timestamp
8182
assert(Timestamp(2021, 01, 29, 12, 42, 44).withOffset(7 * 60 + 30) == Timestamp.fromISOString("20210129T201244+0730"));
8283
static assert(Timestamp(2021, 01, 29, 4, 42, 44).withOffset(- (7 * 60 + 30)) == Timestamp.fromISOExtString("2021-01-28T21:12:44-07:30"));
8384

84-
assert(Timestamp("T0740Z") == Timestamp.onlyTime(7, 40));
85-
assert(Timestamp("T074030Z") == Timestamp.onlyTime(7, 40, 30));
86-
assert(Timestamp("T074030.056Z") == Timestamp.onlyTime(7, 40, 30, -3, 56));
85+
assert(Timestamp("T0740") == Timestamp.onlyTime(7, 40));
86+
assert(Timestamp("T074030Z") == Timestamp.onlyTime(7, 40, 30).withOffset(0));
87+
assert(Timestamp("T074030.056") == Timestamp.onlyTime(7, 40, 30, -3, 56));
8788

88-
assert(Timestamp("07:40Z") == Timestamp.onlyTime(7, 40));
89-
assert(Timestamp("07:40:30Z") == Timestamp.onlyTime(7, 40, 30));
90-
assert(Timestamp("T07:40:30.056Z") == Timestamp.onlyTime(7, 40, 30, -3, 56));
89+
assert(Timestamp("07:40") == Timestamp.onlyTime(7, 40));
90+
assert(Timestamp("07:40:30") == Timestamp.onlyTime(7, 40, 30));
91+
assert(Timestamp("T07:40:30.056Z") == Timestamp.onlyTime(7, 40, 30, -3, 56).withOffset(0));
9192
}
9293

93-
version(all)
94-
{
95-
short offset;
96-
}
97-
else
98-
/+
94+
private short _offset = short.min;
95+
96+
/++
9997
If the time in UTC is known, but the offset to local time is unknown, this can be represented with an offset of “-00:00”.
10098
This differs semantically from an offset of “Z” or “+00:00”, which imply that UTC is the preferred reference point for the specified time.
10199
RFC2822 describes a similar convention for email.
102-
private short _offset;
103100
+/
101+
/++
102+
Timezone offset in minutes
103+
+/
104+
short offset()() const @safe pure nothrow @nogc @property
104105
{
106+
return isLocalTime ? 0 : _offset;
107+
}
105108

106-
/++
107-
Timezone offset in minutes
108-
+/
109-
short offset() const @safe pure nothrow @nogc @property
110-
{
111-
return _offset >> 1;
112-
}
109+
///
110+
void offset()(ushort offset) @safe pure nothrow @nogc @property
111+
{
112+
_offset = offset;
113+
}
113114

114-
// /++
115-
// Returns: true if timezone has offset
116-
// +/
117-
// bool hasOffset() const @safe pure nothrow @nogc @property
118-
// {
119-
// return _offset & 1;
120-
// }
115+
/++
116+
Returns: true if it is a local time
117+
+/
118+
bool isLocalTime()() const @safe pure nothrow @nogc @property
119+
{
120+
return _offset == _offset.min;
121+
}
122+
123+
/++
124+
+/
125+
void setLocalTimezone()() @safe pure nothrow @nogc @property
126+
{
127+
_offset = _offset.min;
121128
}
122129

130+
123131
/++
124132
Year
125133
+/
@@ -337,7 +345,7 @@ struct Timestamp
337345
import std.datetime.date : TimeOfDay;
338346
auto dt = TimeOfDay(7, 14, 30);
339347
Timestamp ts = dt;
340-
assert(dt.toISOExtString ~ "Z" == ts.toString);
348+
assert(dt.toISOExtString == ts.toString);
341349
assert(dt == cast(TimeOfDay) ts);
342350
}
343351

@@ -354,20 +362,23 @@ struct Timestamp
354362
import std.datetime.date : DateTime;
355363
auto dt = DateTime(1982, 4, 1, 20, 59, 22);
356364
Timestamp ts = dt;
357-
assert(dt.toISOExtString ~ "Z" == ts.toString);
365+
assert(dt.toISOExtString == ts.toString);
358366
assert(dt == cast(DateTime) ts);
359367
}
360368

361369
///
362370
this(SysTime)(const SysTime systime)
363371
if (SysTime.stringof == "SysTime")
364372
{
365-
auto utcTime = systime.toUTC;
366-
this = fromUnixTime(utcTime.toUnixTime);
373+
import std.datetime.timezone : LocalTime;
374+
auto isLocal = systime.timezone is LocalTime();
375+
auto thisTimes = isLocal ? systime : systime.toUTC;
376+
this = fromUnixTime(thisTimes.toUnixTime);
367377
this.fractionExponent = -7;
368-
this.fractionCoefficient = utcTime.fracSecs.total!"hnsecs";
378+
this.fractionCoefficient = thisTimes.fracSecs.total!"hnsecs";
369379
this.precision = Precision.fraction;
370-
this.offset = cast(short) systime.utcOffset.total!"minutes";
380+
if (!isLocal)
381+
this.offset = cast(short) systime.utcOffset.total!"minutes";
371382
}
372383

373384
///
@@ -423,15 +434,15 @@ struct Timestamp
423434
auto duration = 5.weeks + 2.days + 7.hours + 40.minutes + 4.seconds + 9876543.hnsecs;
424435
Timestamp ts = duration;
425436

426-
assert(ts.toISOExtString == `0005-02-88T07:40:04.9876543Z`);
437+
assert(ts.toISOExtString == `0005-02-88T07:40:04.9876543`);
427438
assert(duration == cast(Duration) ts);
428439

429440
duration = -duration;
430441
ts = Timestamp(duration);
431-
assert(ts.toISOExtString == `0005-02-99T07:40:04.9876543Z`);
442+
assert(ts.toISOExtString == `0005-02-99T07:40:04.9876543`);
432443
assert(duration == cast(Duration) ts);
433444

434-
assert(Timestamp(Duration.zero).toISOExtString == `0000-00-88T00:00:00.0000000Z`);
445+
assert(Timestamp(Duration.zero).toISOExtString == `0000-00-88T00:00:00.0000000`);
435446
}
436447

437448
/++
@@ -580,13 +591,9 @@ struct Timestamp
580591
import core.time : hnsecs, minutes;
581592
import std.datetime.date: DateTime;
582593
import std.datetime.systime: SysTime;
583-
import std.datetime.timezone: UTC, SimpleTimeZone;
584-
auto ret = SysTime.fromUnixTime(toUnixTime, UTC()) + getFraction!7.hnsecs;
585-
if (offset)
586-
{
587-
ret = ret.toOtherTZ(new immutable SimpleTimeZone(offset.minutes));
588-
}
589-
return ret;
594+
import std.datetime.timezone: UTC, LocalTime, SimpleTimeZone;
595+
auto timezone = isLocalTime ? LocalTime() : !offset ? UTC() : new immutable SimpleTimeZone(offset.minutes);
596+
return SysTime.fromUnixTime(toUnixTime, timezone) + getFraction!7.hnsecs;
590597
}
591598
else
592599
static if (T.stringof == "Duration")
@@ -710,23 +717,24 @@ struct Timestamp
710717
version (mir_test)
711718
@safe pure nothrow unittest
712719
{
713-
assert(Timestamp.init.toString == "0000T");
714-
assert(Timestamp(2010, 7, 4).toString == "2010-07-04");
715-
assert(Timestamp(1998, 12, 25).toString == "1998-12-25");
716-
assert(Timestamp(0, 1, 5).toString == "0000-01-05");
717-
assert(Timestamp(-4, 1, 5).toString == "-0004-01-05");
720+
import mir.test;
721+
Timestamp.init.toString.should == "0000T";
722+
Timestamp(2010, 7, 4).toString.should == "2010-07-04";
723+
Timestamp(1998, 12, 25).toString.should == "1998-12-25";
724+
Timestamp(0, 1, 5).toString.should == "0000-01-05";
725+
Timestamp(-4, 1, 5).toString.should == "-0004-01-05";
718726

719727
// yyyy-mm-ddThh:mm:ss[.mmm]±hh:mm
720-
assert(Timestamp(2021).toString == "2021T");
721-
assert(Timestamp(2021, 01).toString == "2021-01T", Timestamp(2021, 01).toString);
722-
assert(Timestamp(2021, 01, 29).toString == "2021-01-29");
723-
assert(Timestamp(2021, 01, 29, 19, 42).toString == "2021-01-29T19:42Z");
724-
assert(Timestamp(2021, 01, 29, 12, 42, 44).withOffset(7 * 60).toString == "2021-01-29T19:42:44+07", Timestamp(2021, 01, 29, 12, 42, 44).withOffset(7 * 60).toString);
725-
assert(Timestamp(2021, 01, 29, 12, 42, 44).withOffset(7 * 60 + 30).toString == "2021-01-29T20:12:44+07:30");
726-
727-
assert(Timestamp.onlyTime(7, 40).toString == "07:40Z");
728-
assert(Timestamp.onlyTime(7, 40, 30).toString == "07:40:30Z");
729-
assert(Timestamp.onlyTime(7, 40, 30, -3, 56).toString == "07:40:30.056Z");
728+
Timestamp(2021).toString.should == "2021T";
729+
Timestamp(2021, 01).toString.should == "2021-01T";
730+
Timestamp(2021, 01, 29).toString.should == "2021-01-29";
731+
Timestamp(2021, 01, 29, 19, 42).withOffset(0).toString.should == "2021-01-29T19:42Z";
732+
Timestamp(2021, 01, 29, 12, 42, 44).withOffset(7 * 60).toString.should == "2021-01-29T19:42:44+07";
733+
Timestamp(2021, 01, 29, 12, 42, 44).withOffset(7 * 60 + 30).toString.should == "2021-01-29T20:12:44+07:30";
734+
735+
Timestamp.onlyTime(7, 40).toString.should == "07:40";
736+
Timestamp.onlyTime(7, 40, 30).toString.should == "07:40:30";
737+
Timestamp.onlyTime(7, 40, 30, -3, 56).withOffset(0).toString.should == "07:40:30.056Z";
730738
}
731739

732740
///
@@ -748,9 +756,9 @@ struct Timestamp
748756
assert(Timestamp(-9999, 7, 4).toISOExtString == "-9999-07-04");
749757
assert(Timestamp(-10000, 10, 20).toISOExtString == "-10000-10-20");
750758

751-
assert(Timestamp.onlyTime(7, 40).toISOExtString == "07:40Z");
752-
assert(Timestamp.onlyTime(7, 40, 30).toISOExtString == "07:40:30Z");
753-
assert(Timestamp.onlyTime(7, 40, 30, -3, 56).toISOExtString == "07:40:30.056Z");
759+
assert(Timestamp.onlyTime(7, 40).toISOExtString == "07:40");
760+
assert(Timestamp.onlyTime(7, 40, 30).toISOExtString == "07:40:30");
761+
assert(Timestamp.onlyTime(7, 40, 30, -3, 56).toISOExtString == "07:40:30.056");
754762

755763
const cdate = Timestamp(1999, 7, 6);
756764
immutable idate = Timestamp(1999, 7, 6);
@@ -786,14 +794,14 @@ struct Timestamp
786794
assert(Timestamp(2021).toISOString == "2021T");
787795
assert(Timestamp(2021, 01).toISOString == "2021-01T"); // always extended
788796
assert(Timestamp(2021, 01, 29).toISOString == "20210129");
789-
assert(Timestamp(2021, 01, 29, 19, 42).toISOString == "20210129T1942Z");
797+
assert(Timestamp(2021, 01, 29, 19, 42).toISOString == "20210129T1942");
790798
assert(Timestamp(2021, 01, 29, 12, 42, 44).withOffset(7 * 60).toISOString == "20210129T194244+07");
791799
assert(Timestamp(2021, 01, 29, 12, 42, 44).withOffset(7 * 60 + 30).toISOString == "20210129T201244+0730");
792800
static assert(Timestamp(2021, 01, 29, 12, 42, 44).withOffset(7 * 60 + 30).toISOString == "20210129T201244+0730");
793801

794-
assert(Timestamp.onlyTime(7, 40).toISOString == "T0740Z");
795-
assert(Timestamp.onlyTime(7, 40, 30).toISOString == "T074030Z");
796-
assert(Timestamp.onlyTime(7, 40, 30, -3, 56).toISOString == "T074030.056Z");
802+
assert(Timestamp.onlyTime(7, 40).toISOString == "T0740");
803+
assert(Timestamp.onlyTime(7, 40, 30).toISOString == "T074030");
804+
assert(Timestamp.onlyTime(7, 40, 30, -3, 56).toISOString == "T074030.056");
797805
}
798806

799807
///
@@ -957,6 +965,9 @@ struct Timestamp
957965
}
958966
}
959967

968+
if (t.isLocalTime)
969+
return;
970+
960971
if (t.offset == 0)
961972
{
962973
w.put('Z');
@@ -1011,9 +1022,9 @@ struct Timestamp
10111022
// assert(Timestamp(2021, 01) == Timestamp.fromISOString("2021-01T"));
10121023
assert(Timestamp(2021, 01, 29) == Timestamp.fromISOString("20210129"));
10131024
assert(Timestamp(2021, 01, 29, 19, 42) == Timestamp.fromISOString("20210129T1942"));
1014-
assert(Timestamp(2021, 01, 29, 19, 42) == Timestamp.fromISOString("20210129T1942Z"));
1025+
assert(Timestamp(2021, 01, 29, 19, 42).withOffset(0) == Timestamp.fromISOString("20210129T1942Z"));
10151026
assert(Timestamp(2021, 01, 29, 19, 42, 12) == Timestamp.fromISOString("20210129T194212"));
1016-
assert(Timestamp(2021, 01, 29, 19, 42, 12, -3, 67) == Timestamp.fromISOString("20210129T194212.067Z"));
1027+
assert(Timestamp(2021, 01, 29, 19, 42, 12, -3, 67).withOffset(0) == Timestamp.fromISOString("20210129T194212.067Z"));
10171028
assert(Timestamp(2021, 01, 29, 12, 42, 44).withOffset(7 * 60) == Timestamp.fromISOString("20210129T194244+07"));
10181029
assert(Timestamp(2021, 01, 29, 12, 42, 44).withOffset(7 * 60 + 30) == Timestamp.fromISOString("20210129T201244+0730"));
10191030
static assert(Timestamp(2021, 01, 29, 12, 42, 44).withOffset(7 * 60 + 30) == Timestamp.fromISOString("20210129T201244+0730"));
@@ -1127,9 +1138,9 @@ struct Timestamp
11271138
assert(Timestamp(2021, 01) == Timestamp.fromISOExtString("2021-01T"));
11281139
assert(Timestamp(2021, 01, 29) == Timestamp.fromISOExtString("2021-01-29"));
11291140
assert(Timestamp(2021, 01, 29, 19, 42) == Timestamp.fromISOExtString("2021-01-29T19:42"));
1130-
assert(Timestamp(2021, 01, 29, 19, 42) == Timestamp.fromISOExtString("2021-01-29T19:42Z"));
1141+
assert(Timestamp(2021, 01, 29, 19, 42).withOffset(0) == Timestamp.fromISOExtString("2021-01-29T19:42Z"));
11311142
assert(Timestamp(2021, 01, 29, 19, 42, 12) == Timestamp.fromISOExtString("2021-01-29T19:42:12"));
1132-
assert(Timestamp(2021, 01, 29, 19, 42, 12, -3, 67) == Timestamp.fromISOExtString("2021-01-29T19:42:12.067Z"));
1143+
assert(Timestamp(2021, 01, 29, 19, 42, 12, -3, 67).withOffset(0) == Timestamp.fromISOExtString("2021-01-29T19:42:12.067Z"));
11331144
assert(Timestamp(2021, 01, 29, 12, 42, 44).withOffset(7 * 60) == Timestamp.fromISOExtString("2021-01-29T19:42:44+07"));
11341145
assert(Timestamp(2021, 01, 29, 12, 42, 44).withOffset(7 * 60 + 30) == Timestamp.fromISOExtString("2021-01-29T20:12:44+07:30"));
11351146
static assert(Timestamp(2021, 01, 29, 12, 42, 44).withOffset(7 * 60 + 30) == Timestamp.fromISOExtString("2021-01-29T20:12:44+07:30"));
@@ -1469,7 +1480,10 @@ struct Timestamp
14691480
}
14701481

14711482
if (str == "Z")
1483+
{
1484+
value.offset = 0;
14721485
return true;
1486+
}
14731487

14741488
int hour;
14751489
int minute;
@@ -1532,6 +1546,6 @@ unittest
15321546
ts.fractionCoefficient = nanosec;
15331547
ts.fractionExponent = -9;
15341548
}
1535-
auto ts2 = "1900-01-01T00:00:00.000000000Z".Timestamp;
1549+
auto ts2 = "1900-01-01T00:00:00.000000000".Timestamp;
15361550
assert(ts == ts2);
15371551
}

0 commit comments

Comments
 (0)