Răsfoiți Sursa

Date improvements (#8508)

* add Date tests

* fix timestamp

* fix 2

* pre-1902 dates

* fix typo

* weekday tests, fix python getDay

* fix python getTime

* test fromTime

* update Date documentation, remove fromTime tests

* improve doc, disable some tests on specific targets

* fix typo

* disable out-of-range dates

* very low timestamp does not work on py/win

* add UTC methods to core API, improve doc, remove flash specifics

* move flash Date init to Boot

* add UTC methods to js

* add UTC methods to cs

* rewrite java Date (using java.util.Calendar)

* add UTC methods to php

* add UTC methods to python

* add UTC methods to eval

* add UTC methods to neko

* add UTC methods to lua

* add UTC methods to hl

* add UTC methods to cpp

* UTC and timezone offset tests

* fix python on Windows maybe

* fix python on linux maybe

* Windows is bad
Aurel 6 ani în urmă
părinte
comite
e38302aef7

+ 42 - 14
src/macro/eval/evalStdLib.ml

@@ -674,6 +674,8 @@ module StdCrc32 = struct
 end
 end
 
 
 module StdDate = struct
 module StdDate = struct
+	open Unix
+
 	let encode_date d = encode_instance key_Date ~kind:(IDate d)
 	let encode_date d = encode_instance key_Date ~kind:(IDate d)
 
 
 	let this vthis = match vthis with
 	let this vthis = match vthis with
@@ -688,29 +690,33 @@ module StdDate = struct
 		| 19 ->
 		| 19 ->
 			let r = Str.regexp "^\\([0-9][0-9][0-9][0-9]\\)-\\([0-9][0-9]\\)-\\([0-9][0-9]\\) \\([0-9][0-9]\\):\\([0-9][0-9]\\):\\([0-9][0-9]\\)$" in
 			let r = Str.regexp "^\\([0-9][0-9][0-9][0-9]\\)-\\([0-9][0-9]\\)-\\([0-9][0-9]\\) \\([0-9][0-9]\\):\\([0-9][0-9]\\):\\([0-9][0-9]\\)$" in
 			if not (Str.string_match r s 0) then exc_string ("Invalid date format : " ^ s);
 			if not (Str.string_match r s 0) then exc_string ("Invalid date format : " ^ s);
-			let t = catch_unix_error Unix.localtime (Unix.time()) in
-			let t = { t with
+			let t = {
 				tm_year = int_of_string (Str.matched_group 1 s) - 1900;
 				tm_year = int_of_string (Str.matched_group 1 s) - 1900;
 				tm_mon = int_of_string (Str.matched_group 2 s) - 1;
 				tm_mon = int_of_string (Str.matched_group 2 s) - 1;
 				tm_mday = int_of_string (Str.matched_group 3 s);
 				tm_mday = int_of_string (Str.matched_group 3 s);
 				tm_hour = int_of_string (Str.matched_group 4 s);
 				tm_hour = int_of_string (Str.matched_group 4 s);
 				tm_min = int_of_string (Str.matched_group 5 s);
 				tm_min = int_of_string (Str.matched_group 5 s);
 				tm_sec = int_of_string (Str.matched_group 6 s);
 				tm_sec = int_of_string (Str.matched_group 6 s);
+				tm_wday = 0;
+				tm_yday = 0;
+				tm_isdst = false;
 			} in
 			} in
-			encode_date (fst (catch_unix_error Unix.mktime t))
+			encode_date (fst (catch_unix_error mktime t))
 		| 10 ->
 		| 10 ->
 			let r = Str.regexp "^\\([0-9][0-9][0-9][0-9]\\)-\\([0-9][0-9]\\)-\\([0-9][0-9]\\)$" in
 			let r = Str.regexp "^\\([0-9][0-9][0-9][0-9]\\)-\\([0-9][0-9]\\)-\\([0-9][0-9]\\)$" in
 			if not (Str.string_match r s 0) then exc_string ("Invalid date format : " ^ s);
 			if not (Str.string_match r s 0) then exc_string ("Invalid date format : " ^ s);
-			let t = catch_unix_error Unix.localtime (Unix.time()) in
-			let t = { t with
+			let t = {
 				tm_year = int_of_string (Str.matched_group 1 s) - 1900;
 				tm_year = int_of_string (Str.matched_group 1 s) - 1900;
 				tm_mon = int_of_string (Str.matched_group 2 s) - 1;
 				tm_mon = int_of_string (Str.matched_group 2 s) - 1;
 				tm_mday = int_of_string (Str.matched_group 3 s);
 				tm_mday = int_of_string (Str.matched_group 3 s);
 				tm_hour = 0;
 				tm_hour = 0;
 				tm_min = 0;
 				tm_min = 0;
 				tm_sec = 0;
 				tm_sec = 0;
+				tm_wday = 0;
+				tm_yday = 0;
+				tm_isdst = false;
 			} in
 			} in
-			encode_date (fst (catch_unix_error Unix.mktime t))
+			encode_date (fst (catch_unix_error mktime t))
 		| 8 ->
 		| 8 ->
 			let r = Str.regexp "^\\([0-9][0-9]\\):\\([0-9][0-9]\\):\\([0-9][0-9]\\)$" in
 			let r = Str.regexp "^\\([0-9][0-9]\\):\\([0-9][0-9]\\):\\([0-9][0-9]\\)$" in
 			if not (Str.string_match r s 0) then exc_string ("Invalid date format : " ^ s);
 			if not (Str.string_match r s 0) then exc_string ("Invalid date format : " ^ s);
@@ -723,15 +729,29 @@ module StdDate = struct
 			exc_string ("Invalid date format : " ^ s)
 			exc_string ("Invalid date format : " ^ s)
 	)
 	)
 
 
-	let getDate = vifun0 (fun vthis -> vint (catch_unix_error Unix.localtime (this vthis)).tm_mday)
-	let getDay = vifun0 (fun vthis -> vint (catch_unix_error Unix.localtime (this vthis)).tm_wday)
-	let getFullYear = vifun0 (fun vthis -> vint (((catch_unix_error Unix.localtime (this vthis)).tm_year) + 1900))
-	let getHours = vifun0 (fun vthis -> vint (catch_unix_error Unix.localtime (this vthis)).tm_hour)
-	let getMinutes = vifun0 (fun vthis -> vint (catch_unix_error Unix.localtime (this vthis)).tm_min)
-	let getMonth = vifun0 (fun vthis -> vint (catch_unix_error Unix.localtime (this vthis)).tm_mon)
-	let getSeconds = vifun0 (fun vthis -> vint (catch_unix_error Unix.localtime (this vthis)).tm_sec)
+	let getDate = vifun0 (fun vthis -> vint (catch_unix_error localtime (this vthis)).tm_mday)
+	let getDay = vifun0 (fun vthis -> vint (catch_unix_error localtime (this vthis)).tm_wday)
+	let getFullYear = vifun0 (fun vthis -> vint (((catch_unix_error localtime (this vthis)).tm_year) + 1900))
+	let getHours = vifun0 (fun vthis -> vint (catch_unix_error localtime (this vthis)).tm_hour)
+	let getMinutes = vifun0 (fun vthis -> vint (catch_unix_error localtime (this vthis)).tm_min)
+	let getMonth = vifun0 (fun vthis -> vint (catch_unix_error localtime (this vthis)).tm_mon)
+	let getSeconds = vifun0 (fun vthis -> vint (catch_unix_error localtime (this vthis)).tm_sec)
+	let getUTCDate = vifun0 (fun vthis -> vint (catch_unix_error gmtime (this vthis)).tm_mday)
+	let getUTCDay = vifun0 (fun vthis -> vint (catch_unix_error gmtime (this vthis)).tm_wday)
+	let getUTCFullYear = vifun0 (fun vthis -> vint (((catch_unix_error gmtime (this vthis)).tm_year) + 1900))
+	let getUTCHours = vifun0 (fun vthis -> vint (catch_unix_error gmtime (this vthis)).tm_hour)
+	let getUTCMinutes = vifun0 (fun vthis -> vint (catch_unix_error gmtime (this vthis)).tm_min)
+	let getUTCMonth = vifun0 (fun vthis -> vint (catch_unix_error gmtime (this vthis)).tm_mon)
+	let getUTCSeconds = vifun0 (fun vthis -> vint (catch_unix_error gmtime (this vthis)).tm_sec)
 	let getTime = vifun0 (fun vthis -> vfloat ((this vthis) *. 1000.))
 	let getTime = vifun0 (fun vthis -> vfloat ((this vthis) *. 1000.))
-	let now = vfun0 (fun () -> encode_date (catch_unix_error Unix.time()))
+	let getTimezoneOffset = vifun0 (fun vthis ->
+		let tmLocal = catch_unix_error localtime (this vthis) in
+		let tmUTC = catch_unix_error gmtime (this vthis) in
+		let tsLocal = fst (catch_unix_error mktime tmLocal) in
+		let tsUTC = fst (catch_unix_error mktime tmUTC) in
+		vint (int_of_float ((tsUTC -. tsLocal) /. 60.))
+	)
+	let now = vfun0 (fun () -> encode_date (catch_unix_error time()))
 	let toString = vifun0 (fun vthis -> vstring (s_date (this vthis)))
 	let toString = vifun0 (fun vthis -> vstring (s_date (this vthis)))
 end
 end
 
 
@@ -3312,7 +3332,15 @@ let init_standard_library builtins =
 		"getMinutes",StdDate.getMinutes;
 		"getMinutes",StdDate.getMinutes;
 		"getMonth",StdDate.getMonth;
 		"getMonth",StdDate.getMonth;
 		"getSeconds",StdDate.getSeconds;
 		"getSeconds",StdDate.getSeconds;
+		"getUTCDate",StdDate.getUTCDate;
+		"getUTCDay",StdDate.getUTCDay;
+		"getUTCFullYear",StdDate.getUTCFullYear;
+		"getUTCHours",StdDate.getUTCHours;
+		"getUTCMinutes",StdDate.getUTCMinutes;
+		"getUTCMonth",StdDate.getUTCMonth;
+		"getUTCSeconds",StdDate.getUTCSeconds;
 		"getTime",StdDate.getTime;
 		"getTime",StdDate.getTime;
+		"getTimezoneOffset",StdDate.getTimezoneOffset;
 		"toString",StdDate.toString;
 		"toString",StdDate.toString;
 	];
 	];
 	init_fields builtins (["sys";"thread"],"Deque") [] [
 	init_fields builtins (["sys";"thread"],"Deque") [] [

+ 77 - 66
std/Date.hx

@@ -32,7 +32,14 @@
 	There are some extra functions available in the `DateTools` class.
 	There are some extra functions available in the `DateTools` class.
 
 
 	In the context of Haxe dates, a timestamp is defined as the number of
 	In the context of Haxe dates, a timestamp is defined as the number of
-	milliseconds elapsed since 1st January 1970.
+	milliseconds elapsed since 1st January 1970 UTC.
+
+	## Supported range
+
+	Due to platform limitations, only dates in the range 1970 through 2038 are
+	supported consistently. Some targets may support dates outside this range,
+	depending on the OS at runtime. The `Date.fromTime` method will not work with
+	timestamps outside the range on any target.
 **/
 **/
 extern class Date {
 extern class Date {
 	/**
 	/**
@@ -41,7 +48,7 @@ extern class Date {
 		The behaviour of a Date instance is only consistent across platforms if
 		The behaviour of a Date instance is only consistent across platforms if
 		the the arguments describe a valid date.
 		the the arguments describe a valid date.
 
 
-		- month: 0 to 11
+		- month: 0 to 11 (note that this is zero-based)
 		- day: 1 to 31
 		- day: 1 to 31
 		- hour: 0 to 23
 		- hour: 0 to 23
 		- min: 0 to 59
 		- min: 0 to 59
@@ -50,8 +57,11 @@ extern class Date {
 	function new(year:Int, month:Int, day:Int, hour:Int, min:Int, sec:Int):Void;
 	function new(year:Int, month:Int, day:Int, hour:Int, min:Int, sec:Int):Void;
 
 
 	/**
 	/**
-		Returns the timestamp (in milliseconds) of the date. It might
-		only have a per-second precision depending on the platforms.
+		Returns the timestamp (in milliseconds) of `this` date.
+		On cpp and neko, this function only has a second resolution, so the
+		result will always be a multiple of `1000.0`, e.g. `1454698271000.0`.
+		To obtain the current timestamp with better precision on cpp and neko,
+		see the `Sys.time` API.
 
 
 		For measuring time differences with millisecond accuracy on
 		For measuring time differences with millisecond accuracy on
 		all platforms, see `haxe.Timer.stamp`.
 		all platforms, see `haxe.Timer.stamp`.
@@ -59,44 +69,92 @@ extern class Date {
 	function getTime():Float;
 	function getTime():Float;
 
 
 	/**
 	/**
-		Returns the hours of `this` Date (0-23 range).
+		Returns the hours of `this` Date (0-23 range) in the local timezone.
 	**/
 	**/
 	function getHours():Int;
 	function getHours():Int;
 
 
 	/**
 	/**
-		Returns the minutes of `this` Date (0-59 range).
+		Returns the minutes of `this` Date (0-59 range) in the local timezone.
 	**/
 	**/
 	function getMinutes():Int;
 	function getMinutes():Int;
 
 
 	/**
 	/**
-		Returns the seconds of `this` Date (0-59 range).
+		Returns the seconds of `this` Date (0-59 range) in the local timezone.
 	**/
 	**/
 	function getSeconds():Int;
 	function getSeconds():Int;
 
 
 	/**
 	/**
-		Returns the full year of `this` Date (4-digits).
+		Returns the full year of `this` Date (4 digits) in the local timezone.
 	**/
 	**/
 	function getFullYear():Int;
 	function getFullYear():Int;
 
 
 	/**
 	/**
-		Returns the month of `this` Date (0-11 range).
+		Returns the month of `this` Date (0-11 range) in the local timezone.
+		Note that the month number is zero-based.
 	**/
 	**/
 	function getMonth():Int;
 	function getMonth():Int;
 
 
 	/**
 	/**
-		Returns the day of `this` Date (1-31 range).
+		Returns the day of `this` Date (1-31 range) in the local timezone.
 	**/
 	**/
 	function getDate():Int;
 	function getDate():Int;
 
 
 	/**
 	/**
-		Returns the day of the week of `this` Date (0-6 range) where `0` is Sunday.
+		Returns the day of the week of `this` Date (0-6 range, where `0` is Sunday)
+		in the local timezone.
 	**/
 	**/
 	function getDay():Int;
 	function getDay():Int;
 
 
 	/**
 	/**
-		Returns a string representation of `this` Date, by using the
-		standard format [YYYY-MM-DD HH:MM:SS]. See `DateTools.format` for
-		other formating rules.
+		Returns the hours of `this` Date (0-23 range) in UTC.
+	**/
+	function getUTCHours():Int;
+
+	/**
+		Returns the minutes of `this` Date (0-59 range) in UTC.
+	**/
+	function getUTCMinutes():Int;
+
+	/**
+		Returns the seconds of `this` Date (0-59 range) in UTC.
+	**/
+	function getUTCSeconds():Int;
+
+	/**
+		Returns the full year of `this` Date (4 digits) in UTC.
+	**/
+	function getUTCFullYear():Int;
+
+	/**
+		Returns the month of `this` Date (0-11 range) in UTC.
+		Note that the month number is zero-based.
+	**/
+	function getUTCMonth():Int;
+
+	/**
+		Returns the day of `this` Date (1-31 range) in UTC.
+	**/
+	function getUTCDate():Int;
+
+	/**
+		Returns the day of the week of `this` Date (0-6 range, where `0` is Sunday)
+		in UTC.
+	**/
+	function getUTCDay():Int;
+
+	/**
+		Returns the time zone difference of `this` Date in the current locale
+		to UTC, in minutes.
+
+		Assuming the function is executed on a machine in a UTC+2 timezone,
+		`Date.now().getTimezoneOffset()` will return `-120`.
+	**/
+	function getTimezoneOffset():Int;
+
+	/**
+		Returns a string representation of `this` Date in the local timezone
+		using the standard format `YYYY-MM-DD HH:MM:SS`. See `DateTools.format` for
+		other formatting rules.
 	**/
 	**/
 	function toString():String;
 	function toString():String;
 
 
@@ -106,67 +164,20 @@ extern class Date {
 	static function now():Date;
 	static function now():Date;
 
 
 	/**
 	/**
-		Returns a Date from timestamp (in milliseconds) `t`.
+		Creates a Date from the timestamp (in milliseconds) `t`.
 	**/
 	**/
 	static function fromTime(t:Float):Date;
 	static function fromTime(t:Float):Date;
 
 
 	/**
 	/**
-		Returns a Date from a formatted string `s`, with the following accepted
-		formats:
+		Creates a Date from the formatted string `s`. The following formats are
+		accepted by the function:
 
 
 		- `"YYYY-MM-DD hh:mm:ss"`
 		- `"YYYY-MM-DD hh:mm:ss"`
 		- `"YYYY-MM-DD"`
 		- `"YYYY-MM-DD"`
 		- `"hh:mm:ss"`
 		- `"hh:mm:ss"`
 
 
-		The first two formats are expressed in local time, the third in UTC
-		Epoch.
+		The first two formats expressed a date in local time. The third is a time
+		relative to the UTC epoch.
 	**/
 	**/
 	static function fromString(s:String):Date;
 	static function fromString(s:String):Date;
-
-	#if flash
-	private static function __init__():Void
-		untyped {
-			var d:Dynamic = Date;
-			d.now = function() {
-				return __new__(Date);
-			};
-			d.fromTime = function(t) {
-				var d:Date = __new__(Date);
-				d.setTime(t);
-				return d;
-			};
-			d.fromString = function(s:String) {
-				switch (s.length) {
-					case 8: // hh:mm:ss
-						var k = s.split(":");
-						var d:Date = __new__(Date);
-						d.setTime(0);
-						d.setUTCHours(k[0]);
-						d.setUTCMinutes(k[1]);
-						d.setUTCSeconds(k[2]);
-						return d;
-					case 10: // YYYY-MM-DD
-						var k = s.split("-");
-						return new Date(cast k[0], cast k[1] - 1, cast k[2], 0, 0, 0);
-					case 19: // YYYY-MM-DD hh:mm:ss
-						var k = s.split(" ");
-						var y = k[0].split("-");
-						var t = k[1].split(":");
-						return new Date(cast y[0], cast y[1] - 1, cast y[2], cast t[0], cast t[1], cast t[2]);
-					default:
-						throw "Invalid date format : " + s;
-				}
-			};
-			d.prototype[#if (as3 || no_flash_override) "toStringHX" #else "toString" #end] = function() {
-				var date:Date = __this__;
-				var m = date.getMonth() + 1;
-				var d = date.getDate();
-				var h = date.getHours();
-				var mi = date.getMinutes();
-				var s = date.getSeconds();
-				return date.getFullYear() + "-" + (if (m < 10) "0" + m else "" + m) + "-" + (if (d < 10) "0" + d else "" + d) + " "
-					+ (if (h < 10) "0" + h else "" + h) + ":" + (if (mi < 10) "0" + mi else "" + mi) + ":" + (if (s < 10) "0" + s else "" + s);
-			};
-		}
-	#end
 }
 }

+ 33 - 2
std/cpp/_std/Date.hx

@@ -58,6 +58,38 @@
 		return untyped __global__.__hxcpp_get_day(mSeconds);
 		return untyped __global__.__hxcpp_get_day(mSeconds);
 	}
 	}
 
 
+	public function getUTCHours():Int {
+		return untyped __global__.__hxcpp_get_utc_hours(mSeconds);
+	}
+
+	public function getUTCMinutes():Int {
+		return untyped __global__.__hxcpp_get_utc_minutes(mSeconds);
+	}
+
+	public function getUTCSeconds():Int {
+		return untyped __global__.__hxcpp_get_utc_seconds(mSeconds);
+	}
+
+	public function getUTCFullYear():Int {
+		return untyped __global__.__hxcpp_get_utc_year(mSeconds);
+	}
+
+	public function getUTCMonth():Int {
+		return untyped __global__.__hxcpp_get_utc_month(mSeconds);
+	}
+
+	public function getUTCDate():Int {
+		return untyped __global__.__hxcpp_get_utc_date(mSeconds);
+	}
+
+	public function getUTCDay():Int {
+		return untyped __global__.__hxcpp_get_utc_day(mSeconds);
+	}
+
+	public function getTimezoneOffset():Int {
+		return -Std.int((untyped __global__.__hxcpp_timezone_offset(mSeconds)) / 60);
+	}
+
 	public function toString():String {
 	public function toString():String {
 		return untyped __global__.__hxcpp_to_string(mSeconds);
 		return untyped __global__.__hxcpp_to_string(mSeconds);
 	}
 	}
@@ -80,8 +112,7 @@
 		switch (s.length) {
 		switch (s.length) {
 			case 8: // hh:mm:ss
 			case 8: // hh:mm:ss
 				var k = s.split(":");
 				var k = s.split(":");
-				var d:Date = new Date(0, 0, 0, Std.parseInt(k[0]), Std.parseInt(k[1]), Std.parseInt(k[2]));
-				return d;
+				return Date.fromTime(Std.parseInt(k[0]) * 3600000. + Std.parseInt(k[1]) * 60000. + Std.parseInt(k[2]) * 1000.);
 			case 10: // YYYY-MM-DD
 			case 10: // YYYY-MM-DD
 				var k = s.split("-");
 				var k = s.split("-");
 				return new Date(Std.parseInt(k[0]), Std.parseInt(k[1]) - 1, Std.parseInt(k[2]), 0, 0, 0);
 				return new Date(Std.parseInt(k[0]), Std.parseInt(k[1]) - 1, Std.parseInt(k[2]), 0, 0, 0);

+ 47 - 13
std/cs/_std/Date.hx

@@ -21,6 +21,7 @@
  */
  */
 
 
 import cs.system.DateTime;
 import cs.system.DateTime;
+import cs.system.DateTimeKind;
 import cs.system.TimeSpan;
 import cs.system.TimeSpan;
 import haxe.Int64;
 import haxe.Int64;
 
 
@@ -31,17 +32,25 @@ import haxe.Int64;
 	@:readOnly private static var epochTicks:Int64 = new DateTime(1970, 1, 1).Ticks;
 	@:readOnly private static var epochTicks:Int64 = new DateTime(1970, 1, 1).Ticks;
 
 
 	private var date:DateTime;
 	private var date:DateTime;
+	private var dateUTC:DateTime;
 
 
 	@:overload public function new(year:Int, month:Int, day:Int, hour:Int, min:Int, sec:Int):Void {
 	@:overload public function new(year:Int, month:Int, day:Int, hour:Int, min:Int, sec:Int):Void {
 		if (day <= 0)
 		if (day <= 0)
 			day = 1;
 			day = 1;
 		if (year <= 0)
 		if (year <= 0)
 			year = 1;
 			year = 1;
-		date = new DateTime(year, month + 1, day, hour, min, sec);
+		date = new DateTime(year, month + 1, day, hour, min, sec, DateTimeKind.Local);
+		dateUTC = date.ToUniversalTime();
 	}
 	}
 
 
 	@:overload private function new(native:DateTime) {
 	@:overload private function new(native:DateTime) {
-		date = native;
+		if (native.Kind == DateTimeKind.Utc) {
+			dateUTC = native;
+			date = dateUTC.ToLocalTime();
+		} else {
+			date = native;
+			dateUTC = date.ToUniversalTime();
+		}
 	}
 	}
 
 
 	public inline function getTime():Float {
 	public inline function getTime():Float {
@@ -80,14 +89,40 @@ import haxe.Int64;
 		return cast(date.DayOfWeek, Int);
 		return cast(date.DayOfWeek, Int);
 	}
 	}
 
 
+	public inline function getUTCHours():Int {
+		return dateUTC.Hour;
+	}
+
+	public inline function getUTCMinutes():Int {
+		return dateUTC.Minute;
+	}
+
+	public inline function getUTCSeconds():Int {
+		return dateUTC.Second;
+	}
+
+	public inline function getUTCFullYear():Int {
+		return dateUTC.Year;
+	}
+
+	public inline function getUTCMonth():Int {
+		return dateUTC.Month - 1;
+	}
+
+	public inline function getUTCDate():Int {
+		return dateUTC.Day;
+	}
+
+	public inline function getUTCDay():Int {
+		return cast(dateUTC.DayOfWeek, Int);
+	}
+
+	public inline function getTimezoneOffset():Int {
+		return Std.int((cast(dateUTC.Ticks - date.Ticks, Float) / cast(TimeSpan.TicksPerMillisecond, Float)) / 60000.);
+	}
+
 	public function toString():String {
 	public function toString():String {
-		var m = getMonth() + 1;
-		var d = getDate();
-		var h = getHours();
-		var mi = getMinutes();
-		var s = getSeconds();
-		return (getFullYear()) + "-" + (if (m < 10) "0" + m else "" + m) + "-" + (if (d < 10) "0" + d else "" + d) + " " + (if (h < 10) "0" + h else "" + h)
-			+ ":" + (if (mi < 10) "0" + mi else "" + mi) + ":" + (if (s < 10) "0" + s else "" + s);
+		return date.ToString("yyyy-MM-dd HH\\:mm\\:ss");
 	}
 	}
 
 
 	static public inline function now():Date {
 	static public inline function now():Date {
@@ -107,16 +142,15 @@ import haxe.Int64;
 		switch (s.length) {
 		switch (s.length) {
 			case 8: // hh:mm:ss
 			case 8: // hh:mm:ss
 				var k = s.split(":");
 				var k = s.split(":");
-				var d:Date = new Date(1, 1, 1, Std.parseInt(k[0]), Std.parseInt(k[1]), Std.parseInt(k[2]));
-				return d;
+				return new Date(new DateTime(1970, 1, 1, Std.parseInt(k[0]), Std.parseInt(k[1]), Std.parseInt(k[2]), DateTimeKind.Utc));
 			case 10: // YYYY-MM-DD
 			case 10: // YYYY-MM-DD
 				var k = s.split("-");
 				var k = s.split("-");
-				return new Date(Std.parseInt(k[0]), Std.parseInt(k[1]) - 1, Std.parseInt(k[2]), 0, 0, 0);
+				return new Date(new DateTime(Std.parseInt(k[0]), Std.parseInt(k[1]), Std.parseInt(k[2]), 0, 0, 0, DateTimeKind.Local));
 			case 19: // YYYY-MM-DD hh:mm:ss
 			case 19: // YYYY-MM-DD hh:mm:ss
 				var k = s.split(" ");
 				var k = s.split(" ");
 				var y = k[0].split("-");
 				var y = k[0].split("-");
 				var t = k[1].split(":");
 				var t = k[1].split(":");
-				return new Date(Std.parseInt(y[0]), Std.parseInt(y[1]) - 1, Std.parseInt(y[2]), Std.parseInt(t[0]), Std.parseInt(t[1]), Std.parseInt(t[2]));
+				return new Date(new DateTime(Std.parseInt(y[0]), Std.parseInt(y[1]), Std.parseInt(y[2]), Std.parseInt(t[0]), Std.parseInt(t[1]), Std.parseInt(t[2]), DateTimeKind.Local));
 			default:
 			default:
 				throw "Invalid date format : " + s;
 				throw "Invalid date format : " + s;
 		}
 		}

+ 41 - 0
std/flash/Boot.hx

@@ -251,6 +251,47 @@ class Boot extends flash.display.MovieClip {
 
 
 	static function __init__()
 	static function __init__()
 		untyped {
 		untyped {
+			var d:Dynamic = Date;
+			d.now = function() {
+				return __new__(Date);
+			};
+			d.fromTime = function(t) {
+				var d:Date = __new__(Date);
+				d.setTime(t);
+				return d;
+			};
+			d.fromString = function(s:String) {
+				switch (s.length) {
+					case 8: // hh:mm:ss
+						var k = s.split(":");
+						var d:Date = __new__(Date);
+						d.setTime(0);
+						d.setUTCHours(k[0]);
+						d.setUTCMinutes(k[1]);
+						d.setUTCSeconds(k[2]);
+						return d;
+					case 10: // YYYY-MM-DD
+						var k = s.split("-");
+						return new Date(cast k[0], cast k[1] - 1, cast k[2], 0, 0, 0);
+					case 19: // YYYY-MM-DD hh:mm:ss
+						var k = s.split(" ");
+						var y = k[0].split("-");
+						var t = k[1].split(":");
+						return new Date(cast y[0], cast y[1] - 1, cast y[2], cast t[0], cast t[1], cast t[2]);
+					default:
+						throw "Invalid date format : " + s;
+				}
+			};
+			d.prototype[#if (as3 || no_flash_override) "toStringHX" #else "toString" #end] = function() {
+				var date:Date = __this__;
+				var m = date.getMonth() + 1;
+				var d = date.getDate();
+				var h = date.getHours();
+				var mi = date.getMinutes();
+				var s = date.getSeconds();
+				return date.getFullYear() + "-" + (if (m < 10) "0" + m else "" + m) + "-" + (if (d < 10) "0" + d else "" + d) + " "
+					+ (if (h < 10) "0" + h else "" + h) + ":" + (if (mi < 10) "0" + mi else "" + mi) + ":" + (if (s < 10) "0" + s else "" + s);
+			};
 			var aproto = Array.prototype;
 			var aproto = Array.prototype;
 			aproto.copy = function() {
 			aproto.copy = function() {
 				return __this__.slice();
 				return __this__.slice();

+ 56 - 0
std/hl/_std/Date.hx

@@ -75,6 +75,59 @@ import hl.Ref;
 		return v;
 		return v;
 	}
 	}
 
 
+	public function getUTCFullYear():Int {
+		var v = 0;
+		date_get_utc_inf(t, v, null, null, null, null, null, null);
+		return v;
+	}
+
+	public function getUTCMonth():Int {
+		var v = 0;
+		date_get_utc_inf(t, null, v, null, null, null, null, null);
+		return v;
+	}
+
+	public function getUTCDate():Int {
+		var v = 0;
+		date_get_utc_inf(t, null, null, v, null, null, null, null);
+		return v;
+	}
+
+	public function getUTCHours():Int {
+		var v = 0;
+		date_get_utc_inf(t, null, null, null, v, null, null, null);
+		return v;
+	}
+
+	public function getUTCMinutes():Int {
+		var v = 0;
+		date_get_utc_inf(t, null, null, null, null, v, null, null);
+		return v;
+	}
+
+	public function getUTCSeconds():Int {
+		var v = 0;
+		date_get_utc_inf(t, null, null, null, null, null, v, null);
+		return v;
+	}
+
+	public function getUTCDay():Int {
+		var v = 0;
+		date_get_utc_inf(t, null, null, null, null, null, null, v);
+		return v;
+	}
+
+	public function getTimezoneOffset():Int {
+		var y = 0;
+		var mo = 0;
+		var d = 0;
+		var h = 0;
+		var m = 0;
+		var s = 0;
+		date_get_utc_inf(t, y, mo, d, h, m, s, null);
+		return Std.int((date_new(y, mo, d, h, m, s) - t) / 60);
+	}
+
 	@:keep public function toString():String {
 	@:keep public function toString():String {
 		var outLen = 0;
 		var outLen = 0;
 		var bytes = date_to_string(t, outLen);
 		var bytes = date_to_string(t, outLen);
@@ -133,6 +186,9 @@ import hl.Ref;
 	@:hlNative
 	@:hlNative
 	static function date_get_inf(t:Int, year:Ref<Int>, month:Ref<Int>, day:Ref<Int>, hours:Ref<Int>, minutes:Ref<Int>, seconds:Ref<Int>, wday:Ref<Int>):Void {}
 	static function date_get_inf(t:Int, year:Ref<Int>, month:Ref<Int>, day:Ref<Int>, hours:Ref<Int>, minutes:Ref<Int>, seconds:Ref<Int>, wday:Ref<Int>):Void {}
 
 
+	@:hlNative
+	static function date_get_utc_inf(t:Int, year:Ref<Int>, month:Ref<Int>, day:Ref<Int>, hours:Ref<Int>, minutes:Ref<Int>, seconds:Ref<Int>, wday:Ref<Int>):Void {}
+
 	@:hlNative
 	@:hlNative
 	static function date_to_string(t:Int, outLen:Ref<Int>):hl.Bytes {
 	static function date_to_string(t:Int, outLen:Ref<Int>):hl.Bytes {
 		return null;
 		return null;

+ 61 - 23
std/java/_std/Date.hx

@@ -23,68 +23,107 @@
 package;
 package;
 
 
 import haxe.Int64;
 import haxe.Int64;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.TimeZone;
 
 
-@:SuppressWarnings("deprecation")
 @:coreApi class Date {
 @:coreApi class Date {
-	private var date:java.util.Date;
+	private var date:Calendar;
+	private var dateUTC:Calendar;
 
 
 	public function new(year:Int, month:Int, day:Int, hour:Int, min:Int, sec:Int):Void {
 	public function new(year:Int, month:Int, day:Int, hour:Int, min:Int, sec:Int):Void {
-		// issue #1769
-		year = year != 0 ? year - 1900 : 0;
-		date = new java.util.Date(year, month, day, hour, min, sec);
+		date = new GregorianCalendar(year, month, day, hour, min, sec);
+		dateUTC = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
+		dateUTC.setTimeInMillis(date.getTimeInMillis());
 	}
 	}
 
 
 	public inline function getTime():Float {
 	public inline function getTime():Float {
-		return cast date.getTime();
+		return cast date.getTimeInMillis();
 	}
 	}
 
 
 	public inline function getHours():Int {
 	public inline function getHours():Int {
-		return date.getHours();
+		return date.get(Calendar.HOUR_OF_DAY);
 	}
 	}
 
 
 	public inline function getMinutes():Int {
 	public inline function getMinutes():Int {
-		return date.getMinutes();
+		return date.get(Calendar.MINUTE);
 	}
 	}
 
 
 	public inline function getSeconds():Int {
 	public inline function getSeconds():Int {
-		return date.getSeconds();
+		return date.get(Calendar.SECOND);
 	}
 	}
 
 
 	public inline function getFullYear():Int {
 	public inline function getFullYear():Int {
-		return date.getYear() + 1900;
+		return date.get(Calendar.YEAR);
 	}
 	}
 
 
 	public inline function getMonth():Int {
 	public inline function getMonth():Int {
-		return date.getMonth();
+		return date.get(Calendar.MONTH);
 	}
 	}
 
 
 	public inline function getDate():Int {
 	public inline function getDate():Int {
-		return date.getDate();
+		return date.get(Calendar.DAY_OF_MONTH);
 	}
 	}
 
 
 	public inline function getDay():Int {
 	public inline function getDay():Int {
-		return date.getDay();
+		// SUNDAY in Java == 1, MONDAY == 2, ...
+		return cast date.get(Calendar.DAY_OF_WEEK) - 1;
+	}
+
+	public inline function getUTCHours():Int {
+		return dateUTC.get(Calendar.HOUR_OF_DAY);
+	}
+
+	public inline function getUTCMinutes():Int {
+		return dateUTC.get(Calendar.MINUTE);
+	}
+
+	public inline function getUTCSeconds():Int {
+		return dateUTC.get(Calendar.SECOND);
+	}
+
+	public inline function getUTCFullYear():Int {
+		return dateUTC.get(Calendar.YEAR);
+	}
+
+	public inline function getUTCMonth():Int {
+		return dateUTC.get(Calendar.MONTH);
+	}
+
+	public inline function getUTCDate():Int {
+		return dateUTC.get(Calendar.DAY_OF_MONTH);
+	}
+
+	public inline function getUTCDay():Int {
+		// SUNDAY in Java == 1, MONDAY == 2, ...
+		return cast dateUTC.get(Calendar.DAY_OF_WEEK) - 1;
+	}
+
+	public inline function getTimezoneOffset():Int {
+		return -Std.int(date.get(Calendar.ZONE_OFFSET) / 60000);
 	}
 	}
 
 
 	public function toString():String {
 	public function toString():String {
-		var m = date.getMonth() + 1;
-		var d = date.getDate();
-		var h = date.getHours();
-		var mi = date.getMinutes();
-		var s = date.getSeconds();
-		return (date.getYear() + 1900) + "-" + (if (m < 10) "0" + m else "" + m) + "-" + (if (d < 10) "0" + d else "" + d) + " "
+		var m = getMonth() + 1;
+		var d = getDate();
+		var h = getHours();
+		var mi = getMinutes();
+		var s = getSeconds();
+		return getFullYear() + "-" + (if (m < 10) "0" + m else "" + m) + "-" + (if (d < 10) "0" + d else "" + d) + " "
 			+ (if (h < 10) "0" + h else "" + h) + ":" + (if (mi < 10) "0" + mi else "" + mi) + ":" + (if (s < 10) "0" + s else "" + s);
 			+ (if (h < 10) "0" + h else "" + h) + ":" + (if (mi < 10) "0" + mi else "" + mi) + ":" + (if (s < 10) "0" + s else "" + s);
 	}
 	}
 
 
 	static public function now():Date {
 	static public function now():Date {
 		var d = new Date(0, 0, 0, 0, 0, 0);
 		var d = new Date(0, 0, 0, 0, 0, 0);
-		d.date = new java.util.Date();
+		d.date = Calendar.getInstance();
+		d.dateUTC.setTimeInMillis(d.date.getTimeInMillis());
 		return d;
 		return d;
 	}
 	}
 
 
 	static public function fromTime(t:Float):Date {
 	static public function fromTime(t:Float):Date {
 		var d = new Date(0, 0, 0, 0, 0, 0);
 		var d = new Date(0, 0, 0, 0, 0, 0);
-		d.date = new java.util.Date(cast(t, Int64));
+		d.date.setTimeInMillis(cast t);
+		d.dateUTC.setTimeInMillis(cast t);
 		return d;
 		return d;
 	}
 	}
 
 
@@ -92,8 +131,7 @@ import haxe.Int64;
 		switch (s.length) {
 		switch (s.length) {
 			case 8: // hh:mm:ss
 			case 8: // hh:mm:ss
 				var k = s.split(":");
 				var k = s.split(":");
-				var d:Date = new Date(0, 0, 0, Std.parseInt(k[0]), Std.parseInt(k[1]), Std.parseInt(k[2]));
-				return d;
+				return Date.fromTime(Std.parseInt(k[0]) * 3600000. + Std.parseInt(k[1]) * 60000. + Std.parseInt(k[2]) * 1000.);
 			case 10: // YYYY-MM-DD
 			case 10: // YYYY-MM-DD
 				var k = s.split("-");
 				var k = s.split("-");
 				return new Date(Std.parseInt(k[0]), Std.parseInt(k[1]) - 1, Std.parseInt(k[2]), 0, 0, 0);
 				return new Date(Std.parseInt(k[0]), Std.parseInt(k[1]) - 1, Std.parseInt(k[2]), 0, 0, 0);

+ 9 - 0
std/js/_std/Date.hx

@@ -30,6 +30,15 @@
 	@:pure function getDate():Int;
 	@:pure function getDate():Int;
 	@:pure function getDay():Int;
 	@:pure function getDay():Int;
 
 
+	@:pure function getUTCHours():Int;
+	@:pure function getUTCMinutes():Int;
+	@:pure function getUTCSeconds():Int;
+	@:pure function getUTCFullYear():Int;
+	@:pure function getUTCMonth():Int;
+	@:pure function getUTCDate():Int;
+	@:pure function getUTCDay():Int;
+	@:pure function getTimezoneOffset():Int;
+
 	@:pure inline function toString():String {
 	@:pure inline function toString():String {
 		return @:privateAccess HxOverrides.dateStr(this);
 		return @:privateAccess HxOverrides.dateStr(this);
 	}
 	}

+ 2 - 10
std/lua/Boot.hx

@@ -322,15 +322,7 @@ class Boot {
 		switch (s.length) {
 		switch (s.length) {
 			case 8: // hh:mm:ss
 			case 8: // hh:mm:ss
 				var k = s.split(":");
 				var k = s.split(":");
-				var t = lua.Os.time({
-					year: 0,
-					month: 1,
-					day: 1,
-					hour: Lua.tonumber(k[0]),
-					min: Lua.tonumber(k[1]),
-					sec: Lua.tonumber(k[2])
-				});
-				return std.Date.fromTime(t);
+				return std.Date.fromTime(Lua.tonumber(k[0]) * 3600000. + Lua.tonumber(k[1]) * 60000. + Lua.tonumber(k[2]) * 1000.);
 			case 10: // YYYY-MM-DD
 			case 10: // YYYY-MM-DD
 				var k = s.split("-");
 				var k = s.split("-");
 				return new std.Date(Lua.tonumber(k[0]), Lua.tonumber(k[1]) - 1, Lua.tonumber(k[2]), 0, 0, 0);
 				return new std.Date(Lua.tonumber(k[0]), Lua.tonumber(k[1]) - 1, Lua.tonumber(k[2]), 0, 0, 0);
@@ -338,7 +330,7 @@ class Boot {
 				var k = s.split(" ");
 				var k = s.split(" ");
 				var y = k[0].split("-");
 				var y = k[0].split("-");
 				var t = k[1].split(":");
 				var t = k[1].split(":");
-				return new std.Date(cast y[0], Lua.tonumber(y[1]) - 1, Lua.tonumber(y[2]), Lua.tonumber(t[0]), Lua.tonumber(t[1]), Lua.tonumber(t[2]));
+				return new std.Date(Lua.tonumber(y[0]), Lua.tonumber(y[1]) - 1, Lua.tonumber(y[2]), Lua.tonumber(t[0]), Lua.tonumber(t[1]), Lua.tonumber(t[2]));
 			default:
 			default:
 				throw "Invalid date format : " + s;
 				throw "Invalid date format : " + s;
 		}
 		}

+ 3 - 3
std/lua/Os.hx

@@ -114,9 +114,9 @@ extern class Os {
 	A typedef that matches the date parameter `Os.time()` will accept.
 	A typedef that matches the date parameter `Os.time()` will accept.
 **/
 **/
 typedef TimeParam = {
 typedef TimeParam = {
-	year:Float,
-	month:Float,
-	day:Float,
+	year:Int,
+	month:Int,
+	day:Int,
 	?hour:Int,
 	?hour:Int,
 	?min:Int,
 	?min:Int,
 	?sec:Int,
 	?sec:Int,

+ 29 - 0
std/lua/_std/Date.hx

@@ -21,6 +21,7 @@
  */
  */
 @:coreApi class Date {
 @:coreApi class Date {
 	var d:lua.Os.DateType;
 	var d:lua.Os.DateType;
+	var dUTC:lua.Os.DateType;
 	var t:lua.Time;
 	var t:lua.Time;
 
 
 	public function new(year:Int, month:Int, day:Int, hour:Int, min:Int, sec:Int) {
 	public function new(year:Int, month:Int, day:Int, hour:Int, min:Int, sec:Int) {
@@ -33,6 +34,7 @@
 			sec: sec
 			sec: sec
 		});
 		});
 		d = lua.Os.date("*t", t);
 		d = lua.Os.date("*t", t);
+		dUTC = lua.Os.date("!*t", t);
 	};
 	};
 
 
 	public function getTime():Float
 	public function getTime():Float
@@ -59,6 +61,32 @@
 	public function getDay():Int
 	public function getDay():Int
 		return d.wday - 1;
 		return d.wday - 1;
 
 
+	public function getUTCHours():Int
+		return dUTC.hour;
+
+	public function getUTCMinutes():Int
+		return dUTC.min;
+
+	public function getUTCSeconds():Int
+		return dUTC.sec;
+
+	public function getUTCFullYear():Int
+		return dUTC.year;
+
+	public function getUTCMonth():Int
+		return dUTC.month - 1;
+
+	public function getUTCDate():Int
+		return dUTC.day;
+
+	public function getUTCDay():Int
+		return dUTC.wday - 1;
+
+	public function getTimezoneOffset():Int {
+		var tUTC = lua.Os.time(dUTC);
+		return Std.int((tUTC - t) / 60);
+	}
+
 	public inline function toString():String {
 	public inline function toString():String {
 		return lua.Boot.dateStr(this);
 		return lua.Boot.dateStr(this);
 	}
 	}
@@ -73,6 +101,7 @@
 			lua.Lua.setmetatable(d, untyped {__index: Date.prototype});
 			lua.Lua.setmetatable(d, untyped {__index: Date.prototype});
 			d.t = t / 1000;
 			d.t = t / 1000;
 			d.d = lua.Os.date("*t", Std.int(d.t));
 			d.d = lua.Os.date("*t", Std.int(d.t));
+			d.dUTC = lua.Os.date("!*t", Std.int(d.t));
 		}
 		}
 		return d;
 		return d;
 	}
 	}

+ 36 - 0
std/neko/_std/Date.hx

@@ -62,6 +62,38 @@ import neko.Lib;
 		return Std.parseInt(new String(date_format(__t, untyped "%w".__s)));
 		return Std.parseInt(new String(date_format(__t, untyped "%w".__s)));
 	}
 	}
 
 
+	public function getUTCFullYear():Int {
+		return date_get_utc_day(__t).y;
+	}
+
+	public function getUTCMonth():Int {
+		return date_get_utc_day(__t).m - 1;
+	}
+
+	public function getUTCDate():Int {
+		return date_get_utc_day(__t).d;
+	}
+
+	public function getUTCHours():Int {
+		return date_get_utc_hour(__t).h;
+	}
+
+	public function getUTCMinutes():Int {
+		return date_get_utc_hour(__t).m;
+	}
+
+	public function getUTCSeconds():Int {
+		return date_get_utc_hour(__t).s;
+	}
+
+	public function getUTCDay():Int {
+		return Std.parseInt(new String(date_utc_format(__t, untyped "%w".__s)));
+	}
+
+	public function getTimezoneOffset():Int {
+		return -date_get_tz(__t);
+	}
+
 	@:keep public function toString():String {
 	@:keep public function toString():String {
 		return new String(date_format(__t, null));
 		return new String(date_format(__t, null));
 	}
 	}
@@ -91,10 +123,14 @@ import neko.Lib;
 	static var date_new = Lib.load("std", "date_new", 1);
 	static var date_new = Lib.load("std", "date_new", 1);
 	static var date_now = Lib.load("std", "date_now", 0);
 	static var date_now = Lib.load("std", "date_now", 0);
 	static var date_format = Lib.load("std", "date_format", 2);
 	static var date_format = Lib.load("std", "date_format", 2);
+	static var date_utc_format = Lib.load("std", "date_utc_format", 2);
 	static var date_set_hour = Lib.load("std", "date_set_hour", 4);
 	static var date_set_hour = Lib.load("std", "date_set_hour", 4);
 	static var date_set_day = Lib.load("std", "date_set_day", 4);
 	static var date_set_day = Lib.load("std", "date_set_day", 4);
 	static var date_get_day:Dynamic->{y: Int, m: Int, d: Int} = Lib.load("std", "date_get_day", 1);
 	static var date_get_day:Dynamic->{y: Int, m: Int, d: Int} = Lib.load("std", "date_get_day", 1);
 	static var date_get_hour:Dynamic->{h: Int, m: Int, s: Int} = Lib.load("std", "date_get_hour", 1);
 	static var date_get_hour:Dynamic->{h: Int, m: Int, s: Int} = Lib.load("std", "date_get_hour", 1);
+	static var date_get_utc_day:Dynamic->{y: Int, m: Int, d: Int} = Lib.load("std", "date_get_utc_day", 1);
+	static var date_get_utc_hour:Dynamic->{h: Int, m: Int, s: Int} = Lib.load("std", "date_get_utc_hour", 1);
+	static var date_get_tz = Lib.load("std", "date_get_tz", 1);
 	static var int32_to_float = Lib.load("std", "int32_to_float", 1);
 	static var int32_to_float = Lib.load("std", "int32_to_float", 1);
 	static var int32_add = Lib.load("std", "int32_add", 2);
 	static var int32_add = Lib.load("std", "int32_add", 2);
 	static var int32_shl = Lib.load("std", "int32_shl", 2);
 	static var int32_shl = Lib.load("std", "int32_shl", 2);

+ 5 - 0
std/php/Global.hx

@@ -1301,6 +1301,11 @@ extern class Global {
 	**/
 	**/
 	static function date(format:String, ?timestamp:Int):EitherType<String, Bool>;
 	static function date(format:String, ?timestamp:Int):EitherType<String, Bool>;
 
 
+	/**
+		@see http://php.net/manual/en/function.gmdate.php
+	**/
+	static function gmdate(format:String, ?timestamp:Int):EitherType<String, Bool>;
+
 	/**
 	/**
 		@see http://php.net/manual/en/function.time.php
 		@see http://php.net/manual/en/function.time.php
 	**/
 	**/

+ 48 - 1
std/php/_std/Date.hx

@@ -67,6 +67,39 @@ import php.Syntax.*;
 		return int(date("w", int(__t)));
 		return int(date("w", int(__t)));
 	}
 	}
 
 
+	public function getUTCFullYear():Int {
+		return int(gmdate("Y", int(__t)));
+	}
+
+	public function getUTCMonth():Int {
+		var m:Int = int(gmdate("n", int(__t)));
+		return -1 + m;
+	}
+
+	public function getUTCDate():Int {
+		return int(gmdate("j", int(__t)));
+	}
+
+	public function getUTCHours():Int {
+		return int(gmdate("G", int(__t)));
+	}
+
+	public function getUTCMinutes():Int {
+		return int(gmdate("i", int(__t)));
+	}
+
+	public function getUTCSeconds():Int {
+		return int(gmdate("s", int(__t)));
+	}
+
+	public function getUTCDay():Int {
+		return int(gmdate("w", int(__t)));
+	}
+
+	public function getTimezoneOffset():Int {
+		return -Std.int(int(date("Z", int(__t))) / 60);
+	}
+
 	public function toString():String {
 	public function toString():String {
 		return date("Y-m-d H:i:s", int(__t));
 		return date("Y-m-d H:i:s", int(__t));
 	}
 	}
@@ -88,6 +121,20 @@ import php.Syntax.*;
 	}
 	}
 
 
 	public static function fromString(s:String):Date {
 	public static function fromString(s:String):Date {
-		return fromPhpTime(strtotime(s));
+		switch (s.length) {
+			case 8: // hh:mm:ss
+				var k = s.split(":");
+				return Date.fromTime(Std.parseInt(k[0]) * 3600000. + Std.parseInt(k[1]) * 60000. + Std.parseInt(k[2]) * 1000.);
+			case 10: // YYYY-MM-DD
+				var k = s.split("-");
+				return new Date(Std.parseInt(k[0]), Std.parseInt(k[1]) - 1, Std.parseInt(k[2]), 0, 0, 0);
+			case 19: // YYYY-MM-DD hh:mm:ss
+				var k = s.split(" ");
+				var y = k[0].split("-");
+				var t = k[1].split(":");
+				return new Date(Std.parseInt(y[0]), Std.parseInt(y[1]) - 1, Std.parseInt(y[2]), Std.parseInt(t[0]), Std.parseInt(t[1]), Std.parseInt(t[2]));
+			default:
+				throw "Invalid date format : " + s;
+		}
 	}
 	}
 }
 }

+ 57 - 26
std/python/_std/Date.hx

@@ -22,23 +22,24 @@
 
 
 import python.lib.datetime.Datetime;
 import python.lib.datetime.Datetime;
 import python.lib.datetime.Timedelta;
 import python.lib.datetime.Timedelta;
+import python.lib.datetime.Timezone;
 import python.Syntax;
 import python.Syntax;
 
 
 @:coreApi class Date {
 @:coreApi class Date {
-	static var EPOCH_UTC = Datetime.fromtimestamp(0, python.lib.datetime.Timezone.utc);
-
 	private var date:Datetime;
 	private var date:Datetime;
+	private var dateUTC:Datetime;
 
 
 	public function new(year:Int, month:Int, day:Int, hour:Int, min:Int, sec:Int):Void {
 	public function new(year:Int, month:Int, day:Int, hour:Int, min:Int, sec:Int):Void {
 		if (year < Datetime.min.year)
 		if (year < Datetime.min.year)
 			year = Datetime.min.year;
 			year = Datetime.min.year;
 		if (day == 0)
 		if (day == 0)
 			day = 1;
 			day = 1;
-		date = new Datetime(year, month + 1, day, hour, min, sec, 0);
+		date = makeLocal(new Datetime(year, month + 1, day, hour, min, sec, 0));
+		dateUTC = date.astimezone(Timezone.utc);
 	}
 	}
 
 
 	public inline function getTime():Float {
 	public inline function getTime():Float {
-		return python.lib.Time.mktime(date.timetuple()) * 1000;
+		return date.timestamp() * 1000;
 	}
 	}
 
 
 	public inline function getHours():Int {
 	public inline function getHours():Int {
@@ -66,49 +67,79 @@ import python.Syntax;
 	}
 	}
 
 
 	public inline function getDay():Int {
 	public inline function getDay():Int {
-		return date.isoweekday();
+		return date.isoweekday() % 7;
 	}
 	}
 
 
-	public function toString():String {
-		inline function st(x)
-			return Std.string(x);
+	public inline function getUTCHours():Int {
+		return dateUTC.hour;
+	}
+
+	public inline function getUTCMinutes():Int {
+		return dateUTC.minute;
+	}
+
+	public inline function getUTCSeconds():Int {
+		return dateUTC.second;
+	}
+
+	public inline function getUTCFullYear():Int {
+		return dateUTC.year;
+	}
+
+	public inline function getUTCMonth():Int {
+		return dateUTC.month - 1;
+	}
 
 
-		var m = getMonth() + 1;
-		var d = getDate();
-		var h = getHours();
-		var mi = getMinutes();
-		var s = getSeconds();
-		return st(getFullYear()) + "-" + (if (m < 10) "0" + st(m) else "" + st(m)) + "-" + (if (d < 10) "0" + st(d) else "" + st(d)) + " "
-			+ (if (h < 10) "0" + st(h) else "" + st(h)) + ":" + (if (mi < 10) "0" + st(mi) else "" + st(mi)) + ":" + (if (s < 10) "0" + st(s) else "" + st(s));
+	public inline function getUTCDate():Int {
+		return dateUTC.day;
+	}
+
+	public inline function getUTCDay():Int {
+		return dateUTC.isoweekday() % 7;
+	}
+
+	public function getTimezoneOffset():Int {
+		return -Std.int(Syntax.binop(date.utcoffset(), "/", new Timedelta(0, 60)));
+	}
+
+	public function toString():String {
+		return date.strftime("%Y-%m-%d %H:%M:%S");
 	}
 	}
 
 
 	static public function now():Date {
 	static public function now():Date {
-		var d = new Date(1970, 0, 1, 0, 0, 0);
-		d.date = Datetime.now();
+		var d = new Date(2000, 0, 1, 0, 0, 0);
+		d.date = makeLocal(Datetime.now());
+		d.dateUTC = d.date.astimezone(Timezone.utc);
 		return d;
 		return d;
 	}
 	}
 
 
 	static public function fromTime(t:Float):Date {
 	static public function fromTime(t:Float):Date {
-		var d = new Date(1970, 0, 1, 0, 0, 0);
-		d.date = Datetime.fromtimestamp(t / 1000.0);
+		var d = new Date(2000, 0, 1, 0, 0, 0);
+		d.date = makeLocal(Datetime.fromtimestamp(t / 1000.0));
+		d.dateUTC = d.date.astimezone(Timezone.utc);
 		return d;
 		return d;
 	}
 	}
 
 
-	static function UTC(year:Int, month:Int, day:Int, hour:Int, min:Int, sec:Int):Float {
-		var dt = new Datetime(year, month + 1, day, hour, min, sec, 0, python.lib.datetime.Timezone.utc);
-		return datetimeTimestamp(dt, EPOCH_UTC);
+	static function makeLocal(date:Datetime):Datetime {
+		try {
+			return date.astimezone();
+		} catch (e:Dynamic) {
+			// No way in vanilla Python <=3.5 to get the local timezone
+			// Additionally dates close to the epoch <86400 will throw on astimezone
+			var tzinfo = Datetime.now(Timezone.utc).astimezone().tzinfo;
+			return date.replace({tzinfo: tzinfo});
+		}
 	}
 	}
 
 
-	static function datetimeTimestamp(dt:Datetime, epoch:Datetime):Float {
-		return (Syntax.binop(dt, "-", epoch) : Timedelta).total_seconds() * 1000;
+	static function UTC(year:Int, month:Int, day:Int, hour:Int, min:Int, sec:Int):Float {
+		return new Datetime(year, month + 1, day, hour, min, sec, 0, python.lib.datetime.Timezone.utc).timestamp() * 1000;
 	}
 	}
 
 
 	static public function fromString(s:String):Date {
 	static public function fromString(s:String):Date {
 		switch (s.length) {
 		switch (s.length) {
 			case 8: // hh:mm:ss
 			case 8: // hh:mm:ss
 				var k = s.split(":");
 				var k = s.split(":");
-				var d:Date = new Date(0, 0, 0, Std.parseInt(k[0]), Std.parseInt(k[1]), Std.parseInt(k[2]));
-				return d;
+				return Date.fromTime(Std.parseInt(k[0]) * 3600000. + Std.parseInt(k[1]) * 60000. + Std.parseInt(k[2]) * 1000.);
 			case 10: // YYYY-MM-DD
 			case 10: // YYYY-MM-DD
 				var k = s.split("-");
 				var k = s.split("-");
 				return new Date(Std.parseInt(k[0]), Std.parseInt(k[1]) - 1, Std.parseInt(k[2]), 0, 0, 0);
 				return new Date(Std.parseInt(k[0]), Std.parseInt(k[1]) - 1, Std.parseInt(k[2]), 0, 0, 0);

+ 12 - 2
std/python/lib/datetime/Datetime.hx

@@ -50,13 +50,23 @@ extern class Datetime {
 
 
 	public function timetuple():StructTime;
 	public function timetuple():StructTime;
 	public function strftime(format:String):String;
 	public function strftime(format:String):String;
-	public function replace(?year:Int = 1970, ?month:Int = 1, ?day:Int = 1, ?hour:Int = 0, ?minute:Int = 0, ?second:Int, ?microsecond:Int,
-		?tzinfo:Tzinfo):Datetime;
+	public function replace(kwargs:python.KwArgs<{
+		?year:Int,
+		?month:Int,
+		?day:Int,
+		?hour:Int,
+		?minute:Int,
+		?second:Int,
+		?microsecond:Int,
+		?tzinfo:Tzinfo
+	}>):Datetime;
 	/* 0-6 */
 	/* 0-6 */
 	public function weekday():Int;
 	public function weekday():Int;
 	/* 1-7 */
 	/* 1-7 */
 	public function isoweekday():Int;
 	public function isoweekday():Int;
+	public function utcoffset():Int;
 
 
 	// python 3.3
 	// python 3.3
 	public function timestamp():Float;
 	public function timestamp():Float;
+	public function astimezone(?tz:Tzinfo):Datetime;
 }
 }

+ 161 - 2
tests/unit/src/unitstd/Date.unit.hx

@@ -7,7 +7,6 @@ date.getFullYear() == 1982;
 date.getMonth() == 10;
 date.getMonth() == 10;
 date.getDate() == 10;
 date.getDate() == 10;
 date.getDay() == 3;
 date.getDay() == 3;
-//date.getTime() == 405781340000.;
 date.toString() == "1982-11-10 14:02:20";
 date.toString() == "1982-11-10 14:02:20";
 
 
 var date = Date.fromTime(date.getTime());
 var date = Date.fromTime(date.getTime());
@@ -18,8 +17,168 @@ date.getFullYear() == 1982;
 date.getMonth() == 10;
 date.getMonth() == 10;
 date.getDate() == 10;
 date.getDate() == 10;
 date.getDay() == 3;
 date.getDay() == 3;
-//date.getTime() == 405781340000.;
 date.toString() == "1982-11-10 14:02:20";
 date.toString() == "1982-11-10 14:02:20";
 
 
 var date = Date.fromTime(405781340000);
 var date = Date.fromTime(405781340000);
 date.getTime() == 405781340000;
 date.getTime() == 405781340000;
+date.getUTCHours() == 13;
+date.getUTCMinutes() == 2;
+date.getUTCSeconds() == 20;
+date.getUTCFullYear() == 1982;
+date.getUTCMonth() == 10;
+date.getUTCDate() == 10;
+date.getUTCDay() == 3;
+
+// timezone issues
+var date1 = Date.fromTime(1455555555 * 1000.); // 15 Feb 2016 16:59:15 GMT
+var date2 = new Date(2016, 1, 15, 16, 59, 15);
+#if azure
+date1.getTime() == date2.getTime(); // depends on Azure timezone setting!
+#end
+
+var referenceDate = new Date(1970, 0, 12, 2, 0, 0);
+referenceDate.toString() == "1970-01-12 02:00:00";
+#if azure
+referenceDate.getTime() == 957600000.; // depends on Azure timezone setting!
+#end
+
+var date = new Date(1970, 0, 12, 1, 59, 59);
+date.getTime() < referenceDate.getTime();
+
+// < 1970 (negative timestamp)
+// neko, cpp, and python only fail on Windows
+#if !(hl || eval || neko || cpp || python)
+var date = new Date(1904, 11, 12, 1, 4, 1);
+date.getHours() == 1;
+date.getMinutes() == 4;
+date.getSeconds() == 1;
+date.getFullYear() == 1904;
+date.getMonth() == 11;
+date.getDate() == 12;
+date.getDay() == 1;
+date.getTime() < referenceDate.getTime();
+#end
+
+// < 1902 (negative timestamp, outside of signed 32-bit integer range)
+// lua only fails on Mac
+// python only fails on Windows
+#if !(hl || neko || eval || cpp || lua || python)
+var date = new Date(1888, 0, 1, 15, 4, 2);
+date.getHours() == 15;
+date.getMinutes() == 4;
+date.getSeconds() == 2;
+date.getFullYear() == 1888;
+date.getMonth() == 0;
+date.getDate() == 1;
+date.getDay() == 0;
+date.getTime() < referenceDate.getTime();
+#end
+
+// Y2038 (outside of signed 32-bit integer range)
+#if !neko
+var date = new Date(2039, 0, 1, 1, 59, 59);
+date.getHours() == 1;
+date.getMinutes() == 59;
+date.getSeconds() == 59;
+date.getFullYear() == 2039;
+date.getMonth() == 0;
+date.getDate() == 1;
+date.getDay() == 6;
+date.getTime() > referenceDate.getTime();
+#end
+
+// Y2112 (outside of unsigned 32-bit integer range)
+#if !(hl || neko)
+var date = new Date(2112, 0, 1, 1, 59, 59);
+date.getHours() == 1;
+date.getMinutes() == 59;
+date.getSeconds() == 59;
+date.getFullYear() == 2112;
+date.getMonth() == 0;
+date.getDate() == 1;
+date.getDay() == 5;
+date.getTime() > referenceDate.getTime();
+#end
+
+/*
+// fromTime outside the 1970...2038 range (not supported)
+var date = Date.fromTime(-2052910800.0);
+date.getFullYear() == 1904;
+date.getMonth() == 11;
+date.getDate() == 12; // could fail on very large UTC offsets
+var date = Date.fromTime(-2587294800.0);
+date.getFullYear() == 1888;
+date.getMonth() == 0;
+date.getDate() == 5; // could fail on very large UTC offsets
+var date = Date.fromTime(2177838000.0);
+date.getFullYear() == 2039;
+date.getMonth() == 0;
+date.getDate() == 5; // could fail on very large UTC offsets
+var date = Date.fromTime(4481434800.0);
+date.getFullYear() == 2039;
+date.getMonth() == 0;
+date.getDate() == 5; // could fail on very large UTC offsets
+*/
+
+// weekdays
+(new Date(2019, 6, 1, 12, 0, 0)).getDay() == 1;
+(new Date(2019, 6, 2, 12, 0, 0)).getDay() == 2;
+(new Date(2019, 6, 3, 12, 0, 0)).getDay() == 3;
+(new Date(2019, 6, 4, 12, 0, 0)).getDay() == 4;
+(new Date(2019, 6, 5, 12, 0, 0)).getDay() == 5;
+(new Date(2019, 6, 6, 12, 0, 0)).getDay() == 6;
+(new Date(2019, 6, 7, 12, 0, 0)).getDay() == 0;
+
+// fromString
+var date = Date.fromString("2019-07-08 12:22:00");
+date.getHours() == 12;
+date.getMinutes() == 22;
+date.getSeconds() == 0;
+date.getFullYear() == 2019;
+date.getMonth() == 6;
+date.getDate() == 8;
+date.getDay() == 1;
+
+var date = Date.fromString("2019-03-02");
+date.getHours() == 0;
+date.getMinutes() == 0;
+date.getSeconds() == 0;
+date.getFullYear() == 2019;
+date.getMonth() == 2;
+date.getDate() == 2;
+date.getDay() == 6;
+
+// fromString HH:MM:SS should interpret the time as UTC
+#if python
+// disabled on Windows due to https://bugs.python.org/issue37527
+if (Sys.systemName() != "Windows") {
+#end
+var date = Date.fromString("04:05:06");
+t(date.getUTCHours() == 4);
+t(date.getUTCMinutes() == 5);
+t(date.getUTCSeconds() == 6);
+t(date.getUTCFullYear() == 1970);
+t(date.getUTCMonth() == 0);
+t(date.getUTCDate() == 1);
+t(date.getUTCDay() == 4);
+t(date.getTime() == 14706000.);
+#if python
+}
+#end
+
+// timezone offset
+// see https://en.wikipedia.org/wiki/UTC_offset
+Date.fromString("2015-01-08 12:22:00").getTimezoneOffset() % 15 == 0;
+Date.fromString("2015-02-08 12:22:00").getTimezoneOffset() % 15 == 0;
+Date.fromString("2015-03-08 12:22:00").getTimezoneOffset() % 15 == 0;
+Date.fromString("2015-04-08 12:22:00").getTimezoneOffset() % 15 == 0;
+Date.fromString("2015-05-08 12:22:00").getTimezoneOffset() % 15 == 0;
+Date.fromString("2015-06-08 12:22:00").getTimezoneOffset() % 15 == 0;
+Date.fromString("2015-07-08 12:22:00").getTimezoneOffset() % 15 == 0;
+Date.fromString("2015-08-08 12:22:00").getTimezoneOffset() % 15 == 0;
+Date.fromString("2015-09-08 12:22:00").getTimezoneOffset() % 15 == 0;
+Date.fromString("2015-10-08 12:22:00").getTimezoneOffset() % 15 == 0;
+Date.fromString("2015-11-08 12:22:00").getTimezoneOffset() % 15 == 0;
+Date.fromString("2015-12-08 12:22:00").getTimezoneOffset() % 15 == 0;
+
+Date.fromString("2015-06-15 10:00:00").getTimezoneOffset() == Date.fromString("2014-06-15 10:00:00").getTimezoneOffset();