From 900c346a5ef77413df753bbbae86aa27c13ec1c8 Mon Sep 17 00:00:00 2001 From: Cris Ward Date: Tue, 18 Jul 2017 17:19:26 +0100 Subject: [PATCH] Added time:span type --- spec/db_spec.cr | 3 ++ src/mysql.cr | 2 +- src/mysql/types.cr | 70 ++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 72 insertions(+), 3 deletions(-) diff --git a/spec/db_spec.cr b/spec/db_spec.cr index f09c624..ae072d5 100644 --- a/spec/db_spec.cr +++ b/spec/db_spec.cr @@ -32,6 +32,9 @@ DB::DriverSpecs(MySql::Any).run do sample_value Time.new(2016, 2, 15), "datetime", "TIMESTAMP '2016-02-15 00:00:00.000'" sample_value Time.new(2016, 2, 15, 10, 15, 30), "datetime", "TIMESTAMP '2016-02-15 10:15:30.000'" sample_value Time.new(2016, 2, 15, 10, 15, 30), "timestamp", "TIMESTAMP '2016-02-15 10:15:30.000'" + sample_value Time::Span.new(0), "Time", "TIME('00:00:00')" + sample_value Time::Span.new(10, 25, 21), "Time", "TIME('10:25:21')" + sample_value Time::Span.new(0, 0, 10, 5, 0), "Time", "TIME('00:10:05.000')" DB.open db_url do |db| # needs to check version, microsecond support >= 5.7 diff --git a/src/mysql.cr b/src/mysql.cr index c451384..d8ada9c 100644 --- a/src/mysql.cr +++ b/src/mysql.cr @@ -10,5 +10,5 @@ module MySql end end - alias Any = DB::Any | Int16 | Int8 + alias Any = DB::Any | Int16 | Int8 | Time::Span end diff --git a/src/mysql/types.cr b/src/mysql/types.cr index ee54abc..3261a05 100644 --- a/src/mysql/types.cr +++ b/src/mysql/types.cr @@ -52,6 +52,10 @@ abstract struct MySql::Type MySql::Type::DateTime end + def self.type_for(t : ::Time::Span.class) + MySql::Type::Time + end + def self.type_for(t : ::Nil.class) MySql::Type::Null end @@ -155,8 +159,70 @@ abstract struct MySql::Type end decl_type LongLong, 0x08u8, ::Int64 decl_type Int24, 0x09u8 - decl_type Date, 0x0au8 - decl_type Time, 0x0bu8 + + def self.datetime_read(packet) + pkt = packet.read_byte! + return ::Time.new(0) if pkt < 1 + year = packet.read_fixed_int(2).to_i32 + month = packet.read_byte!.to_i32 + day = packet.read_byte!.to_i32 + return ::Time.new(year, month, day) if pkt < 6 + hour = packet.read_byte!.to_i32 + minute = packet.read_byte!.to_i32 + second = packet.read_byte!.to_i32 + return ::Time.new(year, month, day, hour, minute, second) if pkt < 8 + ms = packet.read_int.to_i32 / 1000 # returns microseconds, time only supports milliseconds + return ::Time.new(year, month, day, hour, minute, second, ms) + end + + def self.datetime_write(packet, v : ::Time) + packet.write_blob UInt8.slice(v.year.to_i16, v.year.to_i16/256, v.month.to_i8, v.day.to_i8, v.hour.to_i8, v.minute.to_i8, v.second.to_i8, v.millisecond*1000, v.millisecond*1000/256, v.millisecond*1000/65536) + end + + decl_type Date, 0x0au8, ::Time do + def self.write(packet, v : ::Time) + self.datetime_write(packet, v) + end + + def self.read(packet) + self.datetime_read(packet) + end + + def self.parse(str : ::String) + raise "TextProtocol::Time not implemented" + end + end + decl_type Time, 0x0bu8, ::Time::Span do + def self.write(packet, v : ::Time::Span) + negative = v.to_i < 0 ? 1 : 0 + d = v.days + raise ArgumentError.new("MYSQL TIME over 34 days cannot be saved - https://dev.mysql.com/doc/refman/5.7/en/time.html") if d > 34 + packet.write_blob UInt8.slice(negative, d.to_i8, (d >> 8).to_i8, (d >> 16).to_i8, (d >> 24).to_i8, v.hours.to_i8, v.minutes.to_i8, v.seconds.to_i8, v.milliseconds*1000, v.milliseconds*1000/256, v.milliseconds*1000/65536) + end + + def self.read(packet) + pkt = packet.read_byte! + return ::Time::Span.new(0) if pkt < 1 + negative = packet.read_byte!.to_i32 + days = packet.read_fixed_int(4) + hour = packet.read_byte!.to_i32 + minute = packet.read_byte!.to_i32 + second = packet.read_byte!.to_i32 + ms = pkt > 8 ? (packet.read_int.to_i32 / 1000) : nil + time = ms ? ::Time::Span.new(days, hour, minute, second, ms) : ::Time::Span.new(days, hour, minute, second) + negative > 0 ? (::Time::Span.new(0) - time) : time + end + + def self.parse(str : ::String) + p "timespan #{str}" + begin + time = ::Time.parse(str, "%H:%M:%S.%L") + rescue + time = ::Time.parse(str, "%H:%M:%S") + end + ::Time::Span.new(time.hour, time.minute, time.second) + end + end decl_type DateTime, 0x0cu8, ::Time do def self.write(packet, v : ::Time) packet.write_blob UInt8.slice(v.year.to_i16, v.year.to_i16/256, v.month.to_i8, v.day.to_i8, v.hour.to_i8, v.minute.to_i8, v.second.to_i8, v.millisecond*1000, v.millisecond*1000/256, v.millisecond*1000/65536)