gnuplot_window.cpp
Go to the documentation of this file.
1 /*
2  * This file is part of ACADO Toolkit.
3  *
4  * ACADO Toolkit -- A Toolkit for Automatic Control and Dynamic Optimization.
5  * Copyright (C) 2008-2014 by Boris Houska, Hans Joachim Ferreau,
6  * Milan Vukov, Rien Quirynen, KU Leuven.
7  * Developed within the Optimization in Engineering Center (OPTEC)
8  * under supervision of Moritz Diehl. All rights reserved.
9  *
10  * ACADO Toolkit is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU Lesser General Public
12  * License as published by the Free Software Foundation; either
13  * version 3 of the License, or (at your option) any later version.
14  *
15  * ACADO Toolkit is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18  * Lesser General Public License for more details.
19  *
20  * You should have received a copy of the GNU Lesser General Public
21  * License along with ACADO Toolkit; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
23  *
24  */
25 
26 
35 
36 #ifdef WIN32
37 #include <windows.h>
38 
39 #define round( value ) floor( value + 0.5 )
40 #else
41 #include <unistd.h>
42 #endif
43 
44 #include <cstdlib>
45 #include <string>
46 
47 using namespace std;
48 
50 
51 //
52 // PUBLIC MEMBER FUNCTIONS:
53 //
54 
55 
57 
58 
60 {
61  gnuPipe = 0;
63 
64  ++counter;
65 }
66 
67 
69  ) : PlotWindow( _frequency )
70 {
71  gnuPipe = 0;
73 
74  ++counter;
75 }
76 
77 
79 {
80  gnuPipe = 0;
81 
82  mouseEvent = arg.mouseEvent;
83 }
84 
85 
87 
88  if( gnuPipe != 0 )
89  fclose(gnuPipe);
90 }
91 
92 
94 
95  if( this != &arg ){
96 
97  PlotWindow::operator=( arg );
98 
99  if( gnuPipe != 0 )
100  fclose(gnuPipe);
101 
102  gnuPipe = 0;
103 
104  mouseEvent = arg.mouseEvent;
105  }
106 
107  return *this;
108 }
109 
110 
112 {
113  return new GnuplotWindow( *this );
114 }
115 
116 
118 {
119  if (gnuPipe != 0)
120  {
121  fclose(gnuPipe);
122  gnuPipe = 0;
123  }
124 
125 #ifndef __NO_PIPES__
126  gnuPipe = popen("gnuplot -persist -background white", "w");
127 
128  // TODO In principle, we should just print out a warning, plot is not going
129  // to generated anyways.
130  if ( !gnuPipe )
132 #endif
133 
134  return SUCCESSFUL_RETURN;
135 }
136 
137 
139  )
140 {
141  if ( ( _frequency == PLOT_IN_ANY_CASE ) || ( _frequency == getPlotFrequency( ) ) )
142  {
143  if( gnuPipe == 0 )
144  {
145  init();
146  }
147 
148  return sendDataToGnuplot( );
149  }
150 
151  return SUCCESSFUL_RETURN;
152 }
153 
154 
156 
158  return SUCCESSFUL_RETURN;
159 }
160 
161 
162 BooleanType GnuplotWindow::getMouseEvent( double &mouseX, double &mouseY )
163 {
164  DVector tmp;
165  if (tmp.read( "mouse.dat" ) != SUCCESSFUL_RETURN)
166  return BT_FALSE;
167 
168  mouseX = tmp( tmp.getDim()-2 );
169  mouseY = tmp( tmp.getDim()-1 );
170 
171  if ( system("rm mouse.dat") )
172  return BT_FALSE;
173 
174  return BT_TRUE;
175 }
176 
177 
178 returnValue GnuplotWindow::waitForMouseEvent( double &mouseX, double &mouseY ){
179 
180  if (gnuPipe == 0)
181  {
183  return SUCCESSFUL_RETURN;
184  }
185 
186  FILE *check;
187  check = fopen( "mouse.dat", "r" );
188 
189  if( check != 0 )
190  {
191  fclose( check );
192 
193  if (system("rm mouse.dat") )
194  return RET_PLOTTING_FAILED;
195  }
196 
197  fprintf(gnuPipe,"pause mouse\n");
198  fflush(gnuPipe);
199  fprintf(gnuPipe,"a = MOUSE_X \n");
200  fflush(gnuPipe);
201  fprintf(gnuPipe,"b = MOUSE_Y \n");
202  fflush(gnuPipe);
203  fprintf(gnuPipe,"save var 'mouse.dat'\n");
204  fflush(gnuPipe);
205 
206  while(1){
207  if( getMouseEvent(mouseX,mouseY) == BT_TRUE ){
208  break;
209  }
210  usleep(20000);
211  }
212  return SUCCESSFUL_RETURN;
213 }
214 
215 
216 
217 //
218 // PROTECTED MEMBER FUNCTIONS:
219 //
220 
221 
223 {
224 #ifndef __NO_PLOTTING__
225 
226  // if gnuPipe is 0: open a temporary file
227  BooleanType toFile = (BooleanType)( gnuPipe == 0 );
228  if( toFile == BT_TRUE )
229  gnuPipe = fopen( "acado2gnuplot_tmp.dat","w" );
230 
231  uint run1, run2, run3, run4;
232 
233  uint nRows;
234  uint nCols;
235 
236  switch ( getNumSubplots( ) )
237  {
238  case 0:
239  nRows = 0;
240  nCols = 0;
241  break;
242 
243  case 1:
244  nRows = 1;
245  nCols = 1;
246  break;
247 
248  case 2:
249  nRows = 2;
250  nCols = 1;
251  break;
252 
253  case 3:
254  case 4:
255  nRows = 2;
256  nCols = 2;
257  break;
258 
259  case 5:
260  case 6:
261  nRows = 3;
262  nCols = 2;
263  break;
264 
265  case 7:
266  case 8:
267  case 9:
268  nRows = 3;
269  nCols = 3;
270  break;
271 
272  default:
273  nRows = 4;
274  nCols = 3;
275  break;
276  }
277 
278  VariableType myType;
279 
280  Grid discretizationGrid;
281  VariablesGrid dataGrid, dataGridX, dataGridTmp;
282  MatrixVariablesGrid dataMatrixGrid;
283 
284  string plotDataString;
285  string userDataString;
286  string plotModeString;
287  string plotStyleString;
288 
289  returnValue returnvalue;
290 
291  fprintf( gnuPipe,"set multiplot;\n" );
292 
293  run1 = 0;
294  for( run3 = 0; run3<nRows; run3++ )
295  {
296  for( run2 = 0; run2<nCols; run2++ )
297  {
298  if( run1 < getNumSubplots( ) )
299  {
300  discretizationGrid.init( );
301 
302  // plot symbolic variables or user-specified variables grids
303  switch ( operator()(run1).getSubPlotType( ) )
304  {
305  // plot symbolic variable
306  case SPT_VARIABLE:
307  if ( getVariableDataGrids( operator()( run1 ).plotVariableY,myType,dataGrid,discretizationGrid ) != SUCCESSFUL_RETURN )
309  break;
310 
312  if ( getVariableDataGrids( operator()( run1 ).plotVariableX,myType,dataGridX,discretizationGrid ) != SUCCESSFUL_RETURN )
314 
315  if ( getVariableDataGrids( operator()( run1 ).plotVariableY,myType,dataGrid,discretizationGrid ) != SUCCESSFUL_RETURN )
317 
318  dataGridX.appendValues( dataGrid );
319  dataGrid = dataGridX;
320  break;
321 
323  if ( getVariableDataGrids( operator()( run1 ).plotVariableX,myType,dataGridX,discretizationGrid ) != SUCCESSFUL_RETURN )
325 
326  if ( getExpressionDataGrids( operator()( run1 ).plotExpressionY,myType,dataGrid,discretizationGrid ) != SUCCESSFUL_RETURN )
328 
329  dataGridX.appendValues( dataGrid );
330  dataGrid = dataGridX;
331  break;
332 
333  // plot user-specified variables grid
334  case SPT_VARIABLES_GRID:
335  if ( getDataGrids( operator()(run1).plotVariablesGrid,myType,dataGrid,discretizationGrid ) != SUCCESSFUL_RETURN )
337  break;
338 
339  case SPT_EXPRESSION:
340  if ( getExpressionDataGrids( operator()( run1 ).plotExpressionY,myType,dataGrid,discretizationGrid ) != SUCCESSFUL_RETURN )
342  break;
343 
345  if ( getExpressionDataGrids( operator()( run1 ).plotExpressionX,myType,dataGridX,discretizationGrid ) != SUCCESSFUL_RETURN )
347 
348  if ( getExpressionDataGrids( operator()( run1 ).plotExpressionY,myType,dataGrid,discretizationGrid ) != SUCCESSFUL_RETURN )
350 
351  dataGridX.appendValues( dataGrid );
352  dataGrid = dataGridX;
353  break;
354 
356  if ( getExpressionDataGrids( operator()( run1 ).plotExpressionX,myType,dataGridX,discretizationGrid ) != SUCCESSFUL_RETURN )
358 
359  if ( getVariableDataGrids( operator()( run1 ).plotVariableY,myType,dataGrid,discretizationGrid ) != SUCCESSFUL_RETURN )
361 
362  dataGridX.appendValues( dataGrid );
363  dataGrid = dataGridX;
364  break;
365 
366  case SPT_ENUM:
367  myType = operator()( run1 ).getYVariableType( );
368 
369  returnvalue = plotDataRecord.getAll( convertPlotToLogName( operator()( run1 ).getPlotEnum( ) ),dataMatrixGrid );
370  if( returnvalue != SUCCESSFUL_RETURN )
371  return ACADOERROR( returnvalue );
372 
373  dataGrid = dataMatrixGrid;
374  break;
375 
376  default:
378  }
379 
380 
381  // define subplot position
382  fprintf(gnuPipe," set size %.16e,%.16e;\n", (1.0 /nCols), (1.0 /nRows) );
383  fprintf(gnuPipe," set origin %.16e,%.16e;\n", (double) run2/((double) nCols),
384  (double) (nRows-1-run3)/((double) nRows) );
385 
386  // define subplot title
387  if ( operator()( run1 ).title.empty() == false )
388  fprintf(gnuPipe," set title '%s'\n", operator()( run1 ).title.c_str() );
389  else
390  fprintf(gnuPipe," set title ' '\n" );
391 
392  // define subplot axes labels
393  if ( operator()( run1 ).xLabel.empty() == false )
394  fprintf(gnuPipe," set xlabel '%s'\n", operator()( run1 ).xLabel.c_str() );
395  else
396  fprintf(gnuPipe," set xlabel ' '\n" );
397 
398  if ( operator()( run1 ).yLabel.empty() == false )
399  fprintf(gnuPipe," set ylabel '%s'\n", operator()( run1 ).yLabel.c_str() );
400  else
401  fprintf(gnuPipe," set ylabel ' '\n" );
402 
403  if( ( acadoIsFinite( operator()( run1 ).xRangeLowerLimit ) == BT_TRUE ) &&
404  ( acadoIsFinite( operator()( run1 ).xRangeUpperLimit ) == BT_TRUE ) )
405  {
406  fprintf(gnuPipe," set xrange [%.16e:%.16e]\n", operator()( run1 ).xRangeLowerLimit, operator()( run1 ).xRangeUpperLimit );
407  }
408  else
409  {
410  fprintf(gnuPipe," set autoscale x\n" );
411  }
412 
413  if( ( acadoIsFinite( operator()( run1 ).yRangeLowerLimit ) == BT_TRUE ) &&
414  ( acadoIsFinite( operator()( run1 ).yRangeUpperLimit ) == BT_TRUE ) )
415  {
416  fprintf(gnuPipe," set yrange [%.16e:%.16e]\n", operator()( run1 ).yRangeLowerLimit, operator()( run1 ).yRangeUpperLimit );
417  }
418  else
419  {
420  if ( ( operator()( run1 ).nLines == 0 ) && ( operator()( run1 ).nData == 0 ) )
421  {
422  double lowerLimit, upperLimit;
423  getAutoScaleYLimits( dataGrid,operator()( run1 ).plotFormat,lowerLimit,upperLimit );
424  fprintf(gnuPipe," set yrange [%.16e:%.16e]\n", lowerLimit, upperLimit );
425  }
426  else
427  {
428  fprintf(gnuPipe," set autoscale y\n" );
429  }
430  }
431 
432  if ( operator()( run1 ).getPlotFormat( ) == PF_LOG )
433  fprintf(gnuPipe," set logscale y\n" );
434 
435  if ( operator()( run1 ).plot3D == BT_FALSE )
436  fprintf(gnuPipe," plot ");
437  else
438  fprintf(gnuPipe," splot ");
439 
440  // plot lines
441  if ( operator()( run1 ).nLines > 0 )
442  for( run4 = 0; run4 < operator()( run1 ).nLines; run4++ )
443  fprintf(gnuPipe,"%.16e title '' lt 2 lw 2,\\\n", operator()( run1 ).lineValues[run4] );
444 
445 
446  getPlotModeString(operator()(run1).plotMode, plotModeString);
447  getPlotStyleString(myType, plotStyleString);
448 
449  switch ( operator()(run1).getSubPlotType( ) )
450  {
451  case SPT_VARIABLE:
452  case SPT_EXPRESSION:
453  if ( discretizationGrid.getNumPoints() > 1 )
454  for( run4=0; run4<discretizationGrid.getNumPoints()-2; ++run4 )
455  fprintf(gnuPipe,"'-' using 1:2 title '' with %s %s,\\\n", plotModeString.c_str(),plotStyleString.c_str() );
456 
457  fprintf(gnuPipe,"'-' using 1:2 title '' with %s %s", plotModeString.c_str(),plotStyleString.c_str() );
458  break;
459 
464  if ( discretizationGrid.getNumPoints() > 1 )
465  for( run4=0; run4<discretizationGrid.getNumPoints()-2; ++run4 )
466  fprintf(gnuPipe,"'-' using 2:3 title '' with %s %s,\\\n", plotModeString.c_str(),plotStyleString.c_str() );
467 
468  fprintf(gnuPipe,"'-' using 2:3 title '' with %s %s", plotModeString.c_str(),plotStyleString.c_str() );
469  break;
470 
471  case SPT_VARIABLES_GRID:
472  if ( dataGrid.getNumValues( ) == 0 )
474 
475  if( operator()( run1 ).plot3D == BT_FALSE )
476  {
477 // for( run4=0; run4<dataGrid.getNumValues( )-1; ++run4 )
478 // fprintf(gnuPipe,"'-' using 1:2 title '' with %s %s,\\\n", plotModeString,plotStyleString );
479  if ( discretizationGrid.getNumPoints() > 1 )
480  for( run4=0; run4<discretizationGrid.getNumPoints()-2; ++run4 )
481  fprintf(gnuPipe,"'-' using 1:2 title '' with %s %s,\\\n", plotModeString.c_str(),plotStyleString.c_str() );
482 
483  fprintf(gnuPipe,"'-' using 1:2 title '' with %s %s", plotModeString.c_str(),plotStyleString.c_str() );
484  }
485  else
486  {
487  if ( dataGrid.getNumValues( ) != 2 )
489 
490  fprintf(gnuPipe,"'-' title '' with %s", plotModeString.c_str() );
491  }
492  break;
493 
494  default:
495  fprintf(gnuPipe,"'-' using 1:2 title '' with %s %s", plotModeString.c_str(),plotStyleString.c_str() );
496  }
497 
498  // plot user-specified data
499  if ( operator()(run1).nData > 0 )
500  {
501  returnvalue = obtainPlotDataString( *(operator()(run1).data[0]),userDataString );
502  if( returnvalue != SUCCESSFUL_RETURN )
503  return ACADOERROR( returnvalue );
504  }
505 
506  if( userDataString.empty() == false )
507  fprintf(gnuPipe,",\\\n'-' using 1:2 title '' with points lt 3 lw 3\n" );
508  else
509  fprintf(gnuPipe,"\n");
510 
511  if ( discretizationGrid.getNumPoints( ) == 0 )
512  {
513  if ( dataGrid.getNumValues( ) == 1 )
514  {
515  returnvalue = obtainPlotDataString( dataGrid,plotDataString );
516  if( returnvalue != SUCCESSFUL_RETURN )
517  return ACADOERROR( returnvalue );
518 
519  fprintf( gnuPipe, "%s", plotDataString.c_str() );
520  fprintf( gnuPipe, "e\n" );
521  }
522  else
523  {
524  for( run4=0; run4<dataGrid.getNumValues( ); ++run4 )
525  {
526  dataGridTmp = dataGrid(run4);
527  returnvalue = obtainPlotDataString( dataGridTmp,plotDataString );
528  if( returnvalue != SUCCESSFUL_RETURN )
529  return ACADOERROR( returnvalue );
530 
531  fprintf( gnuPipe, "%s", plotDataString.c_str() );
532  fprintf( gnuPipe, "e\n" );
533  }
534  }
535  }
536  else
537  {
538  uint startIdx,endIdx;
539 
540  for( run4=0; run4<discretizationGrid.getNumPoints()-1; ++run4 )
541  {
542  startIdx = (uint)round( discretizationGrid.getTime( run4 ) );
543  endIdx = (uint)round( discretizationGrid.getTime( run4+1 ) ) - 1;
544  dataGridTmp = dataGrid.getTimeSubGrid( startIdx,endIdx );
545 
546  returnvalue = obtainPlotDataString( dataGridTmp, plotDataString );
547  if( returnvalue != SUCCESSFUL_RETURN )
548  return ACADOERROR( returnvalue );
549 
550  fprintf( gnuPipe, "%s", plotDataString.c_str() );
551  fprintf( gnuPipe, "e\n" );
552  }
553  }
554 
555  if ( operator()( run1 ).getPlotFormat( ) == PF_LOG )
556  fprintf(gnuPipe," unset logscale y\n" );
557  }
558 
559  fprintf( gnuPipe,"\n" );
560  fflush( gnuPipe );
561 
562  ++run1;
563  }
564  }
565 
566  fprintf( gnuPipe,"unset multiplot\n" );
567 
568  fflush( gnuPipe );
569 
570 
571  // if print to file: end here
572  if ( toFile == BT_TRUE )
573  {
574 
575  fclose( gnuPipe );
576  gnuPipe = 0;
577 
578 #if defined(GNUPLOT_EXECUTABLE) && defined(WIN32)
579  string tmp = string("\"") + string( GNUPLOT_EXECUTABLE ) + string("\" -p acado2gnuplot_tmp.dat");
580 
581  if ( system( tmp.c_str() ) )
582  return RET_PLOTTING_FAILED;
583 
584  if ( system("del acado2gnuplot_tmp.dat") )
585  return RET_PLOTTING_FAILED;
586 #else
587  if ( system("gnuplot -persist -background white acado2gnuplot_tmp.dat") )
588  return RET_PLOTTING_FAILED;
589  if (system("rm -rf acado2gnuplot_tmp.dat") )
590  return RET_PLOTTING_FAILED;
591 #endif
592 
593  return SUCCESSFUL_RETURN;
594  }
595 
596  if( mouseEvent == BT_TRUE )
597  {
598  fprintf(gnuPipe,"pause mouse\n");
599  fflush(gnuPipe);
600 
601  fprintf(gnuPipe,"a = MOUSE_X \n");
602  fflush(gnuPipe);
603 
604  fprintf(gnuPipe,"b = MOUSE_Y \n");
605  fflush(gnuPipe);
606 
607  fprintf(gnuPipe,"save var 'mouse.dat'\n");
608  fflush(gnuPipe);
609  }
610 
611 #endif // __NO_PLOTTING__
612 
613  return SUCCESSFUL_RETURN;
614 }
615 
616 
618  std::string& plotModeString
619  ) const
620 {
621  switch( plotMode )
622  {
623  case PM_LINES:
624  plotModeString = "lines lw 2.5";
625  break;
626 
627  case PM_POINTS:
628  plotModeString = "points pt 2.5";
629  break;
630 
631  default:
632  plotModeString = "lines lw 2.5";
633  }
634 
635  return SUCCESSFUL_RETURN;
636 }
637 
638 
640  std::string& plotStyleString
641  ) const
642 {
643  switch( _type )
644  {
646  plotStyleString = "lt -1"; //black
647  break;
648 
649  case VT_ALGEBRAIC_STATE:
650  plotStyleString = "lt 4"; //magenta
651  break;
652 
653  case VT_PARAMETER:
654  plotStyleString = "lt 8"; //orange
655  break;
656 
657  case VT_CONTROL:
658  plotStyleString = "lt 3"; //blue
659  break;
660 
661  case VT_DISTURBANCE:
662  plotStyleString = "lt 5"; //light blue
663  break;
664 
666  plotStyleString = "lt 2"; //green
667  break;
668 
669  default:
670  //case VT_OUTPUT:
671  plotStyleString = "lt 1"; //red
672  }
673 
674  return SUCCESSFUL_RETURN;
675 }
676 
677 
679  std::string& _plotDataString
680  ) const
681 {
682  stringstream ss;
683 
684  _dataGrid.sprint( ss );
685 
686  _plotDataString = ss.str();
687 
688  return SUCCESSFUL_RETURN;
689 }
690 
691 
693 
694 
695 /*
696  * end of file
697  */
698 
double getTime(uint pointIdx) const
uint getNumSubplots() const
returnValue getPlotModeString(PlotMode plotMode, std::string &plotModeString) const
returnValue waitForMouseEvent(double &mouseX, double &mouseY)
PlotMode
Provides a time grid consisting of vector-valued optimization variables at each grid point...
Allows to pass back messages to the calling function.
virtual returnValue init()
LogName convertPlotToLogName(PlotName _name) const
BEGIN_NAMESPACE_ACADO typedef unsigned int uint
Definition: acado_types.hpp:42
returnValue getVariableDataGrids(const Expression *const variable, VariableType &_type, VariablesGrid &_dataGrid, Grid &_discretizationGrid)
BooleanType mouseEvent
Allows to conveniently handle (one-dimensional) grids consisting of time points.
Definition: grid.hpp:58
virtual PlotWindow * clone() const
#define CLOSE_NAMESPACE_ACADO
VariableType
Definition: acado_types.hpp:95
PlotFrequency
returnValue sprint(std::ostream &stream)
returnValue appendValues(const VariablesGrid &arg)
virtual ~GnuplotWindow()
Allows to setup and plot user-specified plot windows for algorithmic outputs.
Definition: plot_window.hpp:76
returnValue getExpressionDataGrids(const Expression *const expression, VariableType &_type, VariablesGrid &_dataGrid, Grid &_discretizationGrid)
PlotWindow & operator=(const PlotWindow &rhs)
Provides a time grid consisting of matrix-valued optimization variables at each grid point...
LogRecord plotDataRecord
unsigned getDim() const
Definition: vector.hpp:172
returnValue waitForMouseEvents()
GnuplotWindow & operator=(const GnuplotWindow &arg)
VariablesGrid getTimeSubGrid(uint startIdx, uint endIdx) const
returnValue init(uint _nPoints=0, const double *const _times=0)
Definition: grid.cpp:107
VariableType getYVariableType() const
#define BT_TRUE
Definition: acado_types.hpp:47
static int counter
returnValue obtainPlotDataString(VariablesGrid &_dataGrid, std::string &_plotDataString) const
BooleanType acadoIsFinite(double x, double TOL)
uint getNumPoints() const
#define ACADOWARNING(retval)
#define BEGIN_NAMESPACE_ACADO
#define BT_FALSE
Definition: acado_types.hpp:49
returnValue sendDataToGnuplot()
PlotFrequency getPlotFrequency() const
virtual returnValue replot(PlotFrequency _frequency=PLOT_IN_ANY_CASE)
returnValue getDataGrids(const VariablesGrid *const variablesGrid, VariableType &_type, VariablesGrid &_dataGrid, Grid &_discretizationGrid)
returnValue getAll(LogName _name, MatrixVariablesGrid &values) const
Provides an interface to Gnuplot for plotting algorithmic outputs.
BooleanType getMouseEvent(double &mouseX, double &mouseY)
#define ACADOERROR(retval)
PlotWindowSubplot & operator()(uint idx)
returnValue getPlotStyleString(VariableType _type, std::string &plotStyleString) const
virtual returnValue read(std::istream &stream)
Definition: vector.cpp:251
returnValue getAutoScaleYLimits(const VariablesGrid &dataGridY, PlotFormat plotFormat, double &lowerLimit, double &upperLimit) const


acado
Author(s): Milan Vukov, Rien Quirynen
autogenerated on Mon Jun 10 2019 12:34:39