List:Commits« Previous MessageNext Message »
From:Alexey Kopytov Date:April 28 2007 4:04pm
Subject:bk commit into 5.1 tree (kaa:1.2484)
View as plain text  
Below is the list of changes that have just been committed into a local
5.1 repository of kaa. When kaa does a push these changes will
be propagated to the main repository and, within 24 hours after the
push, to the public repository.
For information on how to access the public repository
see http://dev.mysql.com/doc/mysql/en/installing-source-tree.html

ChangeSet@stripped, 2007-04-28 20:04:03+04:00, kaa@stripped +7 -0
  Merge polly.local:/home/kaa/src/maint/bug24912/my50-bug24912
  into  polly.local:/home/kaa/src/maint/bug24912/my51-bug24912
  MERGE: 1.1810.2686.1

  mysql-test/r/func_math.result@stripped, 2007-04-28 20:04:00+04:00, kaa@stripped +1 -2
    Manual merge.
    MERGE: 1.33.1.3

  mysql-test/r/type_newdecimal.result@stripped, 2007-04-28 20:02:30+04:00, kaa@stripped +0 -0
    Auto merged
    MERGE: 1.36.1.9

  mysql-test/t/func_math.test@stripped, 2007-04-28 20:02:30+04:00, kaa@stripped +0 -0
    Auto merged
    MERGE: 1.27.1.1

  sql/item_func.cc@stripped, 2007-04-28 20:02:30+04:00, kaa@stripped +0 -0
    Auto merged
    MERGE: 1.270.1.58

  sql/item_func.h@stripped, 2007-04-28 20:02:30+04:00, kaa@stripped +0 -0
    Auto merged
    MERGE: 1.136.2.24

  sql/item_strfunc.cc@stripped, 2007-04-28 20:04:00+04:00, kaa@stripped +1 -2
    Manual merge.
    MERGE: 1.261.1.38

  sql/mysql_priv.h@stripped, 2007-04-28 20:02:31+04:00, kaa@stripped +0 -0
    Auto merged
    MERGE: 1.290.1.149

# This is a BitKeeper patch.  What follows are the unified diffs for the
# set of deltas contained in the patch.  The rest of the patch, the part
# that BitKeeper cares about, is below these diffs.
# User:	kaa
# Host:	polly.local
# Root:	/home/kaa/src/maint/bug24912/my51-bug24912/RESYNC

--- 1.365/sql/item_func.cc	2007-03-09 12:39:37 +03:00
+++ 1.366/sql/item_func.cc	2007-04-28 20:02:30 +04:00
@@ -619,6 +619,14 @@ Item *Item_func::get_tmp_table_item(THD 
   return copy_or_same(thd);
 }
 
+double Item_int_func::val_real()
+{
+  DBUG_ASSERT(fixed == 1);
+
+  return unsigned_flag ? (double) ((ulonglong) val_int()) : (double) val_int();
+}
+
+
 String *Item_int_func::val_str(String *str)
 {
   DBUG_ASSERT(fixed == 1);
@@ -801,7 +809,10 @@ double Item_func_numhybrid::val_real()
     return result;
   }
   case INT_RESULT:
-    return (double)int_op();
+  {
+    longlong result= int_op();
+    return unsigned_flag ? (double) ((ulonglong) result) : (double) result;
+  }
   case REAL_RESULT:
     return real_op();
   case STRING_RESULT:
@@ -1361,6 +1372,8 @@ longlong Item_func_mod::int_op()
   DBUG_ASSERT(fixed == 1);
   longlong value=  args[0]->val_int();
   longlong val2= args[1]->val_int();
+  longlong result;
+
   if ((null_value= args[0]->null_value || args[1]->null_value))
     return 0; /* purecov: inspected */
   if (val2 == 0)
@@ -1370,9 +1383,13 @@ longlong Item_func_mod::int_op()
   }
 
   if (args[0]->unsigned_flag)
-    return ((ulonglong) value) % val2;
+    result= args[1]->unsigned_flag ? 
+      ((ulonglong) value) % ((ulonglong) val2) : ((ulonglong) value) % val2;
+  else
+    result= args[1]->unsigned_flag ?
+      value % ((ulonglong) val2) : value % val2;
 
-  return value % val2;
+  return result;
 }
 
 double Item_func_mod::real_op()
@@ -1427,6 +1444,7 @@ void Item_func_mod::fix_length_and_dec()
 {
   Item_num_op::fix_length_and_dec();
   maybe_null= 1;
+  unsigned_flag= args[0]->unsigned_flag;
 }
 
 
@@ -1505,8 +1523,9 @@ double Item_func_abs::real_op()
 longlong Item_func_abs::int_op()
 {
   longlong value= args[0]->val_int();
-  null_value= args[0]->null_value;
-  return value >= 0 ? value : -value;
+  if ((null_value= args[0]->null_value))
+    return 0;
+  return (value >= 0) || unsigned_flag ? value : -value;
 }
 
 
@@ -1527,6 +1546,7 @@ my_decimal *Item_func_abs::decimal_op(my
 void Item_func_abs::fix_length_and_dec()
 {
   Item_func_num1::fix_length_and_dec();
+  unsigned_flag= args[0]->unsigned_flag;
 }
 
 
@@ -1901,6 +1921,10 @@ my_decimal *Item_func_floor::decimal_op(
 
 void Item_func_round::fix_length_and_dec()
 {
+  int      decimals_to_set;
+  longlong val1;
+  bool     val1_unsigned;
+  
   unsigned_flag= args[0]->unsigned_flag;
   if (!args[1]->const_item())
   {
@@ -1909,8 +1933,14 @@ void Item_func_round::fix_length_and_dec
     hybrid_type= REAL_RESULT;
     return;
   }
-  
-  int decimals_to_set= max((int)args[1]->val_int(), 0);
+
+  val1= args[1]->val_int();
+  val1_unsigned= args[1]->unsigned_flag;
+  if (val1 < 0)
+    decimals_to_set= val1_unsigned ? INT_MAX : 0;
+  else
+    decimals_to_set= (val1 > INT_MAX) ? INT_MAX : (int) val1;
+
   if (args[0]->decimals == NOT_FIXED_DEC)
   {
     max_length= args[0]->max_length;
@@ -1927,10 +1957,9 @@ void Item_func_round::fix_length_and_dec
     max_length= float_length(decimals);
     break;
   case INT_RESULT:
-    if (!decimals_to_set &&
-        (truncate || (args[0]->decimal_precision() < DECIMAL_LONGLONG_DIGITS)))
+    if ((!decimals_to_set && truncate) || (args[0]->decimal_precision() < DECIMAL_LONGLONG_DIGITS))
     {
-      int length_can_increase= test(!truncate && (args[1]->val_int() < 0));
+      int length_can_increase= test(!truncate && (val1 < 0) && !val1_unsigned);
       max_length= args[0]->max_length + length_can_increase;
       /* Here we can keep INT_RESULT */
       hybrid_type= INT_RESULT;
@@ -1956,10 +1985,12 @@ void Item_func_round::fix_length_and_dec
   }
 }
 
-double my_double_round(double value, int dec, bool truncate)
+double my_double_round(double value, longlong dec, bool dec_unsigned,
+                       bool truncate)
 {
   double tmp;
-  uint abs_dec= abs(dec);
+  bool dec_negative= (dec < 0) && !dec_unsigned;
+  ulonglong abs_dec= dec_negative ? -dec : dec;
   /*
     tmp2 is here to avoid return the value with 80 bit precision
     This will fix that the test round(0.1,1) = round(0.1,1) is true
@@ -1969,7 +2000,11 @@ double my_double_round(double value, int
   tmp=(abs_dec < array_elements(log_10) ?
        log_10[abs_dec] : pow(10.0,(double) abs_dec));
 
-  if (truncate)
+  if (dec_negative && isinf(tmp))
+    tmp2= 0;
+  else if (!dec_negative && isinf(value * tmp))
+    tmp2= value;
+  else if (truncate)
   {
     if (value >= 0)
       tmp2= dec < 0 ? floor(value/tmp)*tmp : floor(value*tmp)/tmp;
@@ -1985,24 +2020,35 @@ double my_double_round(double value, int
 double Item_func_round::real_op()
 {
   double value= args[0]->val_real();
-  int dec= (int) args[1]->val_int();
 
   if (!(null_value= args[0]->null_value || args[1]->null_value))
-    return my_double_round(value, dec, truncate);
+    return my_double_round(value, args[1]->val_int(), args[1]->unsigned_flag,
+                           truncate);
 
   return 0.0;
 }
 
+/*
+  Rounds a given value to a power of 10 specified as the 'to' argument,
+  avoiding overflows when the value is close to the ulonglong range boundary.
+*/
+
+static inline ulonglong my_unsigned_round(ulonglong value, ulonglong to)
+{
+  ulonglong tmp= value / to * to;
+  return (value - tmp < (to >> 1)) ? tmp : tmp + to;
+}
+
 
 longlong Item_func_round::int_op()
 {
   longlong value= args[0]->val_int();
-  int dec=(int) args[1]->val_int();
+  longlong dec= args[1]->val_int();
   decimals= 0;
-  uint abs_dec;
+  ulonglong abs_dec;
   if ((null_value= args[0]->null_value || args[1]->null_value))
     return 0;
-  if (dec >= 0)
+  if ((dec >= 0) || args[1]->unsigned_flag)
     return value; // integer have not digits after point
 
   abs_dec= -dec;
@@ -2014,21 +2060,12 @@ longlong Item_func_round::int_op()
   tmp= log_10_int[abs_dec];
   
   if (truncate)
-  {
-    if (unsigned_flag)
-      value= (ulonglong(value)/tmp)*tmp;
-    else
-      value= (value/tmp)*tmp;
-  }
+    value= (unsigned_flag) ?
+      ((ulonglong) value / tmp) * tmp : (value / tmp) * tmp;
   else
-  {
-    if (unsigned_flag)
-      value= ((ulonglong(value)+(tmp>>1))/tmp)*tmp;
-    else if ( value >= 0)
-      value= ((value+(tmp>>1))/tmp)*tmp;
-    else
-      value= ((value-(tmp>>1))/tmp)*tmp;
-  }
+    value= (unsigned_flag || value >= 0) ?
+      my_unsigned_round((ulonglong) value, tmp) :
+      -my_unsigned_round((ulonglong) -value, tmp);
   return value;
 }
 
@@ -2036,14 +2073,18 @@ longlong Item_func_round::int_op()
 my_decimal *Item_func_round::decimal_op(my_decimal *decimal_value)
 {
   my_decimal val, *value= args[0]->val_decimal(&val);
-  int dec=(int) args[1]->val_int();
-  if (dec > 0)
+  longlong dec= args[1]->val_int();
+  if (dec > 0 || (dec < 0 && args[1]->unsigned_flag))
   {
-    decimals= min(dec, DECIMAL_MAX_SCALE); // to get correct output
+    dec= min((ulonglong) dec, DECIMAL_MAX_SCALE);
+    decimals= dec; // to get correct output
   }
+  else if (dec < INT_MIN)
+    dec= INT_MIN;
+    
   if (!(null_value= (args[0]->null_value || args[1]->null_value ||
-                     my_decimal_round(E_DEC_FATAL_ERROR, value, dec, truncate,
-                                      decimal_value) > 1)))
+                     my_decimal_round(E_DEC_FATAL_ERROR, value, dec,
+                                      truncate, decimal_value) > 1)))
     return decimal_value;
   return 0;
 }

--- 1.161/sql/item_func.h	2007-01-23 20:45:50 +03:00
+++ 1.162/sql/item_func.h	2007-04-28 20:02:30 +04:00
@@ -280,7 +280,7 @@ public:
   { max_length= 21; }
   Item_int_func(List<Item> &list) :Item_func(list) { max_length= 21; }
   Item_int_func(THD *thd, Item_int_func *item) :Item_func(thd, item) {}
-  double val_real() { DBUG_ASSERT(fixed == 1); return (double) val_int(); }
+  double val_real();
   String *val_str(String*str);
   enum Item_result result_type () const { return INT_RESULT; }
   void fix_length_and_dec() {}
@@ -305,12 +305,6 @@ class Item_func_signed :public Item_int_
 public:
   Item_func_signed(Item *a) :Item_int_func(a) {}
   const char *func_name() const { return "cast_as_signed"; }
-  double val_real()
-  {
-    double tmp= args[0]->val_real();
-    null_value= args[0]->null_value;
-    return tmp;
-  }
   longlong val_int();
   longlong val_int_from_str(int *error);
   void fix_length_and_dec()

--- 1.315/sql/item_strfunc.cc	2007-03-11 11:11:06 +03:00
+++ 1.316/sql/item_strfunc.cc	2007-04-28 20:04:00 +04:00
@@ -1951,7 +1951,7 @@ String *Item_func_format::val_str(String
     double nr= args[0]->val_real();
     if ((null_value=args[0]->null_value))
       return 0; /* purecov: inspected */
-    nr= my_double_round(nr, dec, FALSE);
+    nr= my_double_round(nr, (longlong) dec, FALSE, FALSE);
     /* Here default_charset() is right as this is not an automatic conversion */
     str->set_real(nr, dec, default_charset());
     if (isnan(nr))

--- 1.489/sql/mysql_priv.h	2007-03-17 02:13:17 +03:00
+++ 1.490/sql/mysql_priv.h	2007-04-28 20:02:31 +04:00
@@ -1866,7 +1866,8 @@ ha_rows filesort(THD *thd, TABLE *form,s
                  ha_rows *examined_rows);
 void filesort_free_buffers(TABLE *table, bool full);
 void change_double_for_sort(double nr,byte *to);
-double my_double_round(double value, int dec, bool truncate);
+double my_double_round(double value, longlong dec, bool dec_unsigned,
+                       bool truncate);
 int get_quick_record(SQL_SELECT *select);
 
 int calc_weekday(long daynr,bool sunday_first_day_of_week);

--- 1.53/mysql-test/r/type_newdecimal.result	2007-03-15 12:08:23 +03:00
+++ 1.54/mysql-test/r/type_newdecimal.result	2007-04-28 20:02:30 +04:00
@@ -763,7 +763,7 @@ truncate(9999999999999999999999999999999
 99999999999999999999999999999999999999.000000000000000000000000000000
 select truncate(99.999999999999999999999999999999999999,31);
 truncate(99.999999999999999999999999999999999999,31)
-100.000000000000000000000000000000
+99.999999999999999999999999999999
 select truncate(99999999999999999999999999999999999999,-31);
 truncate(99999999999999999999999999999999999999,-31)
 99999990000000000000000000000000000000

--- 1.40/mysql-test/r/func_math.result	2007-01-23 20:45:49 +03:00
+++ 1.41/mysql-test/r/func_math.result	2007-04-28 20:04:00 +04:00
@@ -155,9 +155,6 @@ select format(col2,6) from t1 where col1
 format(col2,6)
 1,234,567,890,123,456.123450
 drop table t1;
-select round(150, 2);
-round(150, 2)
-150.00
 select ceil(0.09);
 ceil(0.09)
 1
@@ -168,11 +165,11 @@ create table t1 select round(1, 6);
 show create table t1;
 Table	Create Table
 t1	CREATE TABLE `t1` (
-  `round(1, 6)` decimal(7,6) NOT NULL DEFAULT '0.000000'
+  `round(1, 6)` int(1) NOT NULL DEFAULT '0'
 ) ENGINE=MyISAM DEFAULT CHARSET=latin1
 select * from t1;
 round(1, 6)
-1.000000
+1
 drop table t1;
 select abs(-2) * -2;
 abs(-2) * -2
@@ -285,3 +282,91 @@ format(t2.f2-t2.f1+1,0)
 10,000
 drop table t1, t2;
 set names default;
+select cast(-2 as unsigned), 18446744073709551614, -2;
+cast(-2 as unsigned)	18446744073709551614	-2
+18446744073709551614	18446744073709551614	-2
+select abs(cast(-2 as unsigned)), abs(18446744073709551614), abs(-2);
+abs(cast(-2 as unsigned))	abs(18446744073709551614)	abs(-2)
+18446744073709551614	18446744073709551614	2
+select ceiling(cast(-2 as unsigned)), ceiling(18446744073709551614), ceiling(-2);
+ceiling(cast(-2 as unsigned))	ceiling(18446744073709551614)	ceiling(-2)
+18446744073709551614	18446744073709551614	-2
+select floor(cast(-2 as unsigned)), floor(18446744073709551614), floor(-2);
+floor(cast(-2 as unsigned))	floor(18446744073709551614)	floor(-2)
+18446744073709551614	18446744073709551614	-2
+select format(cast(-2 as unsigned), 2), format(18446744073709551614, 2), format(-2, 2);
+format(cast(-2 as unsigned), 2)	format(18446744073709551614, 2)	format(-2, 2)
+18,446,744,073,709,551,614.00	18,446,744,073,709,551,614.00	-2.00
+select sqrt(cast(-2 as unsigned)), sqrt(18446744073709551614), sqrt(-2);
+sqrt(cast(-2 as unsigned))	sqrt(18446744073709551614)	sqrt(-2)
+4294967296	4294967296	NULL
+select round(cast(-2 as unsigned), 1), round(18446744073709551614, 1), round(-2, 1);
+round(cast(-2 as unsigned), 1)	round(18446744073709551614, 1)	round(-2, 1)
+18446744073709551614	18446744073709551614	-2
+select round(4, cast(-2 as unsigned)), round(4, 18446744073709551614), round(4, -2);
+round(4, cast(-2 as unsigned))	round(4, 18446744073709551614)	round(4, -2)
+4	4	0
+select truncate(cast(-2 as unsigned), 1), truncate(18446744073709551614, 1), truncate(-2, 1);
+truncate(cast(-2 as unsigned), 1)	truncate(18446744073709551614, 1)	truncate(-2, 1)
+18446744073709551614	18446744073709551614	-2
+select truncate(4, cast(-2 as unsigned)), truncate(4, 18446744073709551614), truncate(4, -2);
+truncate(4, cast(-2 as unsigned))	truncate(4, 18446744073709551614)	truncate(4, -2)
+4	4	0
+select round(10000000000000000000, -19), truncate(10000000000000000000, -19);
+round(10000000000000000000, -19)	truncate(10000000000000000000, -19)
+10000000000000000000	10000000000000000000
+select round(1e0, -309), truncate(1e0, -309);
+round(1e0, -309)	truncate(1e0, -309)
+0	0
+select round(1e1,308), truncate(1e1, 308);
+round(1e1,308)	truncate(1e1, 308)
+10	10
+select round(1e1, 2147483648), truncate(1e1, 2147483648);
+round(1e1, 2147483648)	truncate(1e1, 2147483648)
+10	10
+select round(1.1e1, 4294967295), truncate(1.1e1, 4294967295);
+round(1.1e1, 4294967295)	truncate(1.1e1, 4294967295)
+11	11
+select round(1.12e1, 4294967296), truncate(1.12e1, 4294967296);
+round(1.12e1, 4294967296)	truncate(1.12e1, 4294967296)
+11.2	11.2
+select round(1.5, 2147483640), truncate(1.5, 2147483640);
+round(1.5, 2147483640)	truncate(1.5, 2147483640)
+1.500000000000000000000000000000	1.500000000000000000000000000000
+select round(1.5, -2147483649), round(1.5, 2147483648);
+round(1.5, -2147483649)	round(1.5, 2147483648)
+0	1.500000000000000000000000000000
+select truncate(1.5, -2147483649), truncate(1.5, 2147483648);
+truncate(1.5, -2147483649)	truncate(1.5, 2147483648)
+0	1.500000000000000000000000000000
+select round(1.5, -4294967296), round(1.5, 4294967296);
+round(1.5, -4294967296)	round(1.5, 4294967296)
+0	1.500000000000000000000000000000
+select truncate(1.5, -4294967296), truncate(1.5, 4294967296);
+truncate(1.5, -4294967296)	truncate(1.5, 4294967296)
+0	1.500000000000000000000000000000
+select round(1.5, -9223372036854775808), round(1.5, 9223372036854775808);
+round(1.5, -9223372036854775808)	round(1.5, 9223372036854775808)
+0	1.500000000000000000000000000000
+select truncate(1.5, -9223372036854775808), truncate(1.5, 9223372036854775808);
+truncate(1.5, -9223372036854775808)	truncate(1.5, 9223372036854775808)
+0	1.500000000000000000000000000000
+select round(1.5, 18446744073709551615), truncate(1.5, 18446744073709551615);
+round(1.5, 18446744073709551615)	truncate(1.5, 18446744073709551615)
+1.500000000000000000000000000000	1.500000000000000000000000000000
+select round(18446744073709551614, -1), truncate(18446744073709551614, -1);
+round(18446744073709551614, -1)	truncate(18446744073709551614, -1)
+18446744073709551610	18446744073709551610
+select round(4, -4294967200), truncate(4, -4294967200);
+round(4, -4294967200)	truncate(4, -4294967200)
+0	0
+select mod(cast(-2 as unsigned), 3), mod(18446744073709551614, 3), mod(-2, 3);
+mod(cast(-2 as unsigned), 3)	mod(18446744073709551614, 3)	mod(-2, 3)
+2	2	-2
+select mod(5, cast(-2 as unsigned)), mod(5, 18446744073709551614), mod(5, -2);
+mod(5, cast(-2 as unsigned))	mod(5, 18446744073709551614)	mod(5, -2)
+5	5	1
+select pow(cast(-2 as unsigned), 5), pow(18446744073709551614, 5), pow(-2, 5);
+pow(cast(-2 as unsigned), 5)	pow(18446744073709551614, 5)	pow(-2, 5)
+2.1359870359209e+96	2.1359870359209e+96	-32
+End of 5.0 tests

--- 1.29/mysql-test/t/func_math.test	2007-01-23 20:45:49 +03:00
+++ 1.30/mysql-test/t/func_math.test	2007-04-28 20:02:30 +04:00
@@ -88,11 +88,6 @@ drop table t1;
 
 
 #
-# Bug #10083 (round doesn't increase decimals)
-#
-select round(150, 2);
-
-#
 # Bug @10632 (Ceiling function returns wrong answer)
 #
 select ceil(0.09);
@@ -196,3 +191,37 @@ select format(t2.f2-t2.f1+1,0) from t1,t
 where t1.f2 = t2.f3 order by t1.f1;
 drop table t1, t2;
 set names default;
+
+# Bug 24912 -- misc functions have trouble with unsigned
+
+select cast(-2 as unsigned), 18446744073709551614, -2;
+select abs(cast(-2 as unsigned)), abs(18446744073709551614), abs(-2);
+select ceiling(cast(-2 as unsigned)), ceiling(18446744073709551614), ceiling(-2);
+select floor(cast(-2 as unsigned)), floor(18446744073709551614), floor(-2);
+select format(cast(-2 as unsigned), 2), format(18446744073709551614, 2), format(-2, 2);
+select sqrt(cast(-2 as unsigned)), sqrt(18446744073709551614), sqrt(-2);
+select round(cast(-2 as unsigned), 1), round(18446744073709551614, 1), round(-2, 1);
+select round(4, cast(-2 as unsigned)), round(4, 18446744073709551614), round(4, -2);
+select truncate(cast(-2 as unsigned), 1), truncate(18446744073709551614, 1), truncate(-2, 1);
+select truncate(4, cast(-2 as unsigned)), truncate(4, 18446744073709551614), truncate(4, -2);
+select round(10000000000000000000, -19), truncate(10000000000000000000, -19);
+select round(1e0, -309), truncate(1e0, -309);
+select round(1e1,308), truncate(1e1, 308);
+select round(1e1, 2147483648), truncate(1e1, 2147483648);
+select round(1.1e1, 4294967295), truncate(1.1e1, 4294967295);
+select round(1.12e1, 4294967296), truncate(1.12e1, 4294967296);
+select round(1.5, 2147483640), truncate(1.5, 2147483640);
+select round(1.5, -2147483649), round(1.5, 2147483648);
+select truncate(1.5, -2147483649), truncate(1.5, 2147483648);
+select round(1.5, -4294967296), round(1.5, 4294967296);
+select truncate(1.5, -4294967296), truncate(1.5, 4294967296);
+select round(1.5, -9223372036854775808), round(1.5, 9223372036854775808);
+select truncate(1.5, -9223372036854775808), truncate(1.5, 9223372036854775808);
+select round(1.5, 18446744073709551615), truncate(1.5, 18446744073709551615);
+select round(18446744073709551614, -1), truncate(18446744073709551614, -1);
+select round(4, -4294967200), truncate(4, -4294967200);
+select mod(cast(-2 as unsigned), 3), mod(18446744073709551614, 3), mod(-2, 3);
+select mod(5, cast(-2 as unsigned)), mod(5, 18446744073709551614), mod(5, -2);
+select pow(cast(-2 as unsigned), 5), pow(18446744073709551614, 5), pow(-2, 5);
+
+--echo End of 5.0 tests
Thread
bk commit into 5.1 tree (kaa:1.2484)Alexey Kopytov28 Apr