diff --git a/corelib/src/date.dart b/corelib/src/date.dart index c3da9d00d94..809530ef59d 100644 --- a/corelib/src/date.dart +++ b/corelib/src/date.dart @@ -112,6 +112,23 @@ interface Date extends Comparable, Hashable default DateImplementation { */ Date changeTimeZone(TimeZone targetTimeZone); + /** + * Returns the abbreviated time-zone name. + * + * Examples: [:"CET":] or [:"CEST":]. + */ + String get timeZoneName(); + + /** + * The time-zone offset is the difference between local time and UTC. That is, + * the offset is positive for time zones west of UTC. + * + * Note, that JavaScript, Python and C return the difference between UTC and + * local time. Java, C# and Ruby return the difference between local time and + * UTC. + */ + Duration get timeZoneOffset(); + /** * Returns the year. */ diff --git a/lib/compiler/implementation/lib/js_helper.dart b/lib/compiler/implementation/lib/js_helper.dart index 9995935b32a..c46f2f42b49 100644 --- a/lib/compiler/implementation/lib/js_helper.dart +++ b/lib/compiler/implementation/lib/js_helper.dart @@ -328,6 +328,19 @@ class Primitives { return JS('String', @'String.fromCharCode.apply(#, #)', null, charCodes); } + static String getTimeZoneName(receiver) { + // When calling toString on a Date it will emit the timezone in parenthesis. + // Example: "Wed May 16 2012 21:13:00 GMT+0200 (CEST)". + // We extract this name using a regexp. + var d = lazyAsJsDate(receiver); + return JS('String', @'/\((.*)\)/.exec(#.toString())[1]', d); + } + + static int getTimeZoneOffsetInMinutes(receiver) { + // Note that JS and Dart disagree on the sign of the offset. + return -JS('int', @'#.getTimezoneOffset()', lazyAsJsDate(receiver)); + } + static valueFromDecomposedDate(years, month, day, hours, minutes, seconds, milliseconds, isUtc) { checkInt(years); diff --git a/lib/compiler/implementation/lib/mockimpl.dart b/lib/compiler/implementation/lib/mockimpl.dart index 2a6171f7adc..5994d6d4547 100644 --- a/lib/compiler/implementation/lib/mockimpl.dart +++ b/lib/compiler/implementation/lib/mockimpl.dart @@ -297,6 +297,16 @@ class DateImplementation implements Date { return new Date.fromEpoch(value, targetTimeZone); } + String get timeZoneName() { + if (isUtc()) return "UTC"; + return Primitives.getTimeZoneName(this); + } + + Duration get timeZoneOffset() { + if (isUtc()) return new Duration(0); + return new Duration(minutes: Primitives.getTimeZoneOffsetInMinutes(this)); + } + int get year() => Primitives.getYear(this); int get month() => Primitives.getMonth(this); diff --git a/runtime/lib/date.cc b/runtime/lib/date.cc index 90158c9a345..0c49e5b3fa0 100644 --- a/runtime/lib/date.cc +++ b/runtime/lib/date.cc @@ -139,6 +139,32 @@ DEFINE_NATIVE_ENTRY(DateNatives_brokenDownToSecondsSinceEpoch, 7) { } +DEFINE_NATIVE_ENTRY(DateNatives_timeZoneName, 1) { + GET_NATIVE_ARGUMENT(Integer, dart_seconds, arguments->At(0)); + int64_t seconds = dart_seconds.AsInt64Value(); + const char* name; + bool succeeded = OS::GetTimeZoneName(seconds, &name); + if (!succeeded) { + UNIMPLEMENTED(); + } + const String& dart_name = String::Handle(String::New(name)); + arguments->SetReturn(dart_name); +} + + +DEFINE_NATIVE_ENTRY(DateNatives_timeZoneOffsetInSeconds, 1) { + GET_NATIVE_ARGUMENT(Integer, dart_seconds, arguments->At(0)); + int64_t seconds = dart_seconds.AsInt64Value(); + int offset; + bool succeeded = OS::GetTimeZoneOffsetInSeconds(seconds, &offset); + if (!succeeded) { + UNIMPLEMENTED(); + } + const Integer& dart_offset = Integer::Handle(Integer::New(offset)); + arguments->SetReturn(dart_offset); +} + + DEFINE_NATIVE_ENTRY(DateNatives_currentTimeMillis, 0) { const Integer& time = Integer::Handle( Integer::New(OS::GetCurrentTimeMillis())); diff --git a/runtime/lib/date.dart b/runtime/lib/date.dart index 3e33c3aa2b2..97395aed124 100644 --- a/runtime/lib/date.dart +++ b/runtime/lib/date.dart @@ -130,6 +130,18 @@ class DateImplementation implements Date { return new Date.fromEpoch(value, targetTimeZone); } + String get timeZoneName() { + if (isUtc()) return "UTC"; + return _timeZoneName(_equivalentSeconds(_secondsSinceEpoch)); + } + + Duration get timeZoneOffset() { + if (isUtc()) return new Duration(0); + int offsetInSeconds = + _timeZoneOffsetInSeconds(_equivalentSeconds(_secondsSinceEpoch)); + return new Duration(seconds: offsetInSeconds); + } + int get year() { int secondsSinceEpoch = _secondsSinceEpoch; // According to V8 some library calls have troubles with negative values. @@ -401,6 +413,12 @@ class DateImplementation implements Date { static int _getCurrentMs() native "DateNatives_currentTimeMillis"; + static String _timeZoneName(int secondsSinceEpoch) + native "DateNatives_timeZoneName"; + + static int _timeZoneOffsetInSeconds(int secondsSinceEpoch) + native "DateNatives_timeZoneOffsetInSeconds"; + // TODO(floitsch): it would be more efficient if we didn't call the native // function for every member, but cached the broken-down date. static int _getYear(int secondsSinceEpoch, bool isUtc) diff --git a/runtime/vm/bootstrap_natives.h b/runtime/vm/bootstrap_natives.h index 8fa219250cf..a681cd6c6b4 100644 --- a/runtime/vm/bootstrap_natives.h +++ b/runtime/vm/bootstrap_natives.h @@ -100,6 +100,8 @@ namespace dart { V(DateNatives_getHours, 2) \ V(DateNatives_getMinutes, 2) \ V(DateNatives_getSeconds, 2) \ + V(DateNatives_timeZoneName, 1) \ + V(DateNatives_timeZoneOffsetInSeconds, 1) \ V(AssertionError_throwNew, 2) \ V(TypeError_throwNew, 5) \ V(FallThroughError_throwNew, 1) \ diff --git a/runtime/vm/os.h b/runtime/vm/os.h index 2c0fbb93cc7..49009838415 100644 --- a/runtime/vm/os.h +++ b/runtime/vm/os.h @@ -42,6 +42,17 @@ class OS { // Returns true if the conversion succeeds, false otherwise. static bool MkTime(tm* tm, int64_t* seconds_result); + // Returns the abbreviated time-zone name for the given instant. + // For example "CET" or "CEST". + static bool GetTimeZoneName(int64_t seconds_since_epoch, + const char** name_result); + + // Returns the difference in seconds between local time and UTC for the given + // instant. + // For example 3600 for CET, and 7200 for CEST. + static bool GetTimeZoneOffsetInSeconds(int64_t seconds_since_epoch, + int* offset_result); + // Returns the current time in milliseconds measured // from midnight January 1, 1970 UTC. static int64_t GetCurrentTimeMillis(); diff --git a/runtime/vm/os_linux.cc b/runtime/vm/os_linux.cc index 2c27fd6ed76..ff857cbe351 100644 --- a/runtime/vm/os_linux.cc +++ b/runtime/vm/os_linux.cc @@ -59,6 +59,27 @@ bool OS::MkTime(tm* tm, int64_t* seconds_result) { } +bool OS::GetTimeZoneName(int64_t seconds_since_epoch, + const char** name_result) { + tm decomposed; + bool succeeded = LocalTime(seconds_since_epoch, &decomposed); + if (!succeeded) return false; + *name_result = decomposed.tm_zone; + return true; +} + + +bool OS::GetTimeZoneOffsetInSeconds(int64_t seconds_since_epoch, + int* offset_result) { + tm decomposed; + bool succeeded = LocalTime(seconds_since_epoch, &decomposed); + if (!succeeded) return false; + // Even if the offset was 24 hours it would still easily fit into 32 bits. + *offset_result = static_cast(decomposed.tm_gmtoff); + return true; +} + + int64_t OS::GetCurrentTimeMillis() { return GetCurrentTimeMicros() / 1000; } diff --git a/runtime/vm/os_macos.cc b/runtime/vm/os_macos.cc index d602485892e..3936e99e93e 100644 --- a/runtime/vm/os_macos.cc +++ b/runtime/vm/os_macos.cc @@ -60,6 +60,27 @@ bool OS::MkTime(tm* tm, int64_t* seconds_result) { } +bool OS::GetTimeZoneName(int64_t seconds_since_epoch, + const char** name_result) { + tm decomposed; + bool succeeded = LocalTime(seconds_since_epoch, &decomposed); + if (!succeeded) return false; + *name_result = decomposed.tm_zone; + return true; +} + + +bool OS::GetTimeZoneOffsetInSeconds(int64_t seconds_since_epoch, + int* offset_result) { + tm decomposed; + bool succeeded = LocalTime(seconds_since_epoch, &decomposed); + if (!succeeded) return false; + // Even if the offset was 24 hours it would still easily fit into 32 bits. + *offset_result = static_cast(decomposed.tm_gmtoff); + return true; +} + + int64_t OS::GetCurrentTimeMillis() { return GetCurrentTimeMicros() / 1000; } diff --git a/runtime/vm/os_win.cc b/runtime/vm/os_win.cc index 96a00359df6..778f9953d8c 100644 --- a/runtime/vm/os_win.cc +++ b/runtime/vm/os_win.cc @@ -18,9 +18,11 @@ bool OS::GmTime(int64_t seconds_since_epoch, tm* tm_result) { } +// As a side-effect sets the globals _timezone, _daylight and _tzname. bool OS::LocalTime(int64_t seconds_since_epoch, tm* tm_result) { time_t seconds = static_cast(seconds_since_epoch); if (seconds != seconds_since_epoch) return false; + // localtime_s implicitly sets _timezone, _daylight and _tzname. errno_t error_code = localtime_s(tm_result, &seconds); return error_code == 0; } @@ -54,6 +56,53 @@ bool OS::MkTime(tm* tm, int64_t* seconds_result) { } +static int GetDaylightSavingBiasInSeconds() { + TIME_ZONE_INFORMATION zone_information; + memset(&zone_information, 0, sizeof(zone_information)); + if (GetTimeZoneInformation(&zone_information) == TIME_ZONE_ID_INVALID) { + // By default the daylight saving offset is an hour. + return -60 * 60; + } else { + return static_cast(zone_information.DaylightBias * 60); + } +} + +bool OS::GetTimeZoneName(int64_t seconds_since_epoch, + const char** name_result) { + tm decomposed; + // LocalTime will set _tzname. + bool succeeded = LocalTime(seconds_since_epoch, &decomposed); + if (!succeeded) return false; + int inDaylightSavingsTime = decomposed.tm_isdst; + if (inDaylightSavingsTime != 0 && inDaylightSavingsTime != 1) { + return false; + } + *name_result = _tzname[inDaylightSavingsTime]; + return true; +} + + +bool OS::GetTimeZoneOffsetInSeconds(int64_t seconds_since_epoch, + int* offset_result) { + tm decomposed; + // LocalTime will set _timezone. + bool succeeded = LocalTime(seconds_since_epoch, &decomposed); + if (!succeeded) return false; + int inDaylightSavingsTime = decomposed.tm_isdst; + if (inDaylightSavingsTime != 0 && inDaylightSavingsTime != 1) { + return false; + } + // Dart and Windows disagree on the sign of the bias. + *offset_result = static_cast(-_timezone); + if (inDaylightSavingsTime == 1) { + static int daylight_bias = GetDaylightSavingBiasInSeconds(); + // Subtract because windows and Dart disagree on the sign. + *offset_result = *offset_result - daylight_bias; + } + return true; +} + + int64_t OS::GetCurrentTimeMillis() { return GetCurrentTimeMicros() / 1000; } diff --git a/tests/corelib/corelib.status b/tests/corelib/corelib.status index ee97f3a2876..00befdaa01e 100644 --- a/tests/corelib/corelib.status +++ b/tests/corelib/corelib.status @@ -34,6 +34,9 @@ string_substring_test: Fail string_test: Fail # Needs index out of range checks. string_replace_dollar_test: Fail +# New methods (timeZoneName and timeZoneOffset) were not ported to frog. +date_time7_test: Skip + [ $compiler == frog && $runtime == drt ] list_sort_test: Fail, Pass # Issue 2667 diff --git a/tests/corelib/date_time7_test.dart b/tests/corelib/date_time7_test.dart new file mode 100644 index 00000000000..da6005e5be4 --- /dev/null +++ b/tests/corelib/date_time7_test.dart @@ -0,0 +1,48 @@ +// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +// Test Date timeZoneName and timeZoneOffset getters. + +testUtc() { + var d = new Date.fromString("2012-03-04T03:25:38.123Z"); + Expect.equals("UTC", d.timeZoneName); + Expect.equals(0, d.timeZoneOffset.inSeconds); +} + +testLocal() { + checkOffset(String name, Duration offset) { + // Timezone abbreviations are not in bijection with their timezones. + // For example AST stands for "Arab Standard Time" (UTC+03), as well as + // "Arabian Standard Time" (UTC+04), or PST stands for Pacific Standard Time + // and Philippine Standard Time. + // + // Hardcode some common timezones. + if (name == "CET") { + Expect.equals(1, offset.inHours); + } else if (name == "CEST") { + Expect.equals(2, offset.inHours); + } else if (name == "GMT") { + Expect.equals(0, offset.inSeconds); + } else if (name == "EST") { + Expect.equals(-5, offset.inHours); + } else if (name == "EDT") { + Expect.equals(-4, offset.inHours); + } else if (name == "PDT") { + Expect.equals(-7, offset.inHours); + } + } + + var d = new Date.fromString("2012-01-02T13:45:23"); + String name = d.timeZoneName; + checkOffset(name, d.timeZoneOffset); + + d = new Date.fromString("2012-07-02T13:45:23"); + name = d.timeZoneName; + checkOffset(name, d.timeZoneOffset); +} + +main() { + testUtc(); + testLocal(); +}