diff -Naur a/resources/themes/bootstrap/3/time-edit-button.png b/resources/themes/bootstrap/3/time-edit-button.png
--- a/resources/themes/bootstrap/3/time-edit-button.png	1970-01-01 01:00:00.000000000 +0100
+++ b/resources/themes/bootstrap/3/time-edit-button.png	2014-12-30 09:51:22.000000000 +0000
@@ -0,0 +1,8 @@
+‰PNG
+
+
IHDR(f¹¬ÓqbKGDÿÿÿ ½§“	pHYs
×
×B(›xtIMEÞ	3ž¸ñ¼IDATxÚíš]H“}À³-7'ó£Œ²XQlÎ:ˆJÊ¤
+RÔe’Ã9¿‚„:â‰¨N:0têhŠ‡ecA(‰‰&Íbs}@¨u�µvoêæîçÈñ>ÔËûx{wp_°£{ãÿ»¯ÿõ¿îëþ1…Çã÷ìÙ³H’†@ÅÔdLV>RHòHúÊ€r
&EEQ”ô‰Åb‚Àèè(555äää ÑhÈÉÉ¡¦¦†ÑÑQA ‹I^CáñxÄ]»v­úÎDQdvv»ÝŽÝnG¥Ra2™ÈÈÈàÇ�ø|>"‘uuuÔÕÕ±eË
+Eâ¶8
+ÑÖÖÆƒ8tè


;vŒ´´4B¡/_¾¤¥¥…––DQ¤¹¹™´´´U¯£ðx<âÎ�;WýÃ±±1Nž<Éþýûikk#???~íÛ·o¤¤¤ðùóg®]»Æë×¯éïï§   q5ØÕÕ…J¥¢¾¾“ÉÄòò2‘H„™™>|ÈÍ›7Ñëõ\½z•JEWW—ôÜ±cÇªï,77£ÑH__ë×¯gdd„>099‰×ë¥¸¸˜+W®PQQ�ßïgff&q5ÑéthµZæææèëë£¿¿A(++ãâÅ‹dee±°°€N§#JZG)µ?¥§§ƒdggc6›ÉËËc~~ž¢¢"6nÜ?L�@€ôôôÄ6ê3gÎðøñc)//çÄ‰9r„p8Ljj*J¥QÄçóqöìYIëH>$UUUD"ìv;^¯…BAjj*™™™h4¼^/v»�H$BUU•¤uÖÙl¶¿t:Ýªï,##ƒÅÅEž<yÂää$:�ŽM›6¡P(<þœÛ·o3::J]]f³•J•¸-V«ÕTWW#Š"‡ƒÚÚZòòòÐét¦¦¦ˆD"Ô××S]]�Z­–´ŽÂãñˆ[·n•TÀ¢(‡ñz½ôôôàr¹ƒ¤§§c6›¹téùùùh4I�¹8`nn®<nÉµœAP>$2 ø<$jµ:y3˜šššÜ[¼¸¸(× (Ê€É
+(Š"¡PˆññqÑëõdgg£×ëilld||œP(´¦iIáñxÄmÛ¶I‚›��ÅápÐÙÙ‰J¥úí{qMM
V«um†UJ„Ãa­­­<x�ÚÚZŽ=ŠF£!3<<Lkk+­­­Ü¸qCºa•’ÁññqŠ‹‹Ù·o---˜L¦øµùùù¸a½~ý:<{öŒ$®»»»Q©TÔÖÖb2™ˆÅbD£QfggéêêâÖ­[lß¾�††T*ÝÝÝ‰­A½^�Á`àÑ£G¨T*^½zÅÇ�™œœÄçóqúôi._¾À¹sçxÿþ=ÓÓÓ‰«Áÿ4¬_¾|ÁårñâÅA ´´”.übX%ÏƒRbÅ°
+‚@VVååå�Æ_k8^»a•f³™¾¾>†††(++ûÅ°®¸À¡¡!¦¦¦¨¨¨Hl£^1¬íííø|>RRRP«Õdee‘––†B¡ÀçóÑÞÞ7¬RB²aÍÌÌü­aMIIáçÏŸô÷÷ÿ9Ã*%4
V«€ÎÎÎÿjX°Z­qo�°6³ò¸[1¬ÝÝÝ¿Öªªª?cX¥Êã–(Ê€2 (®9”>Ÿ/¹“Y+’Y+Ö¯_/oñš×­[—t`ËËË,,,È}PüŸou¡Pˆ‰‰	ššš0äææb0hjjbbbâÏÖ�²Z¸¹¹9œN'N§¥R‰ÑhŒ¿ûý~¢Ñ(‹‹ÅÂæÍ›YZZú×=wå�¬É°:�N:::(((ÀjµRTT7¬###tttÐÑÑ@SS“tÃ*%ƒ˜ÍföîÝËýû÷1�ñk+†uzzšææfÞ¼yƒËåb÷îÝ«Î äìííE©TbµZ1�qÃ:77GOOwîÜA¯×c³ÙP*•ôööJoÔRÂívc4)**ŠÿÙûÓ§O¼{÷Ž©©)N�:E,£°°£ÑˆÛíæîÝ»‰!nX¿~ýÊÓ§O@JJJ8þü?« D£ÑU&’µZí?kii)ƒ�ïß¿SXXÈ†
â‡) ÕjQ*•‰Ë`YY.—‹ááaJJJ8~ü8‡þÅ°ã÷û1›Í‰mÔ•••D£Q~¿ÿ·†Õï÷ãp8ˆF£ñï¯6ÖÙl¶¿¤ô§•ÿ°ºÝnÞ¾}‹N§#'''nX¸wïcccX­VJKKQ*•ÿz›EQ$�®Í°Z,œN'���¿}’Øl6,�†¥¥¥Ä5ê•»‡ÃLMMÑÛÛ‹ÛíF´Z-eeeTVV’——7¬‹‹‹«nÔk”Vy`•e@P”×Ú‡Gc'/IEND®B`‚
\ No newline at end of file
diff -Naur a/resources/themes/bootstrap/3/wt.css b/resources/themes/bootstrap/3/wt.css
--- a/resources/themes/bootstrap/3/wt.css	2014-03-28 10:41:53.000000000 +0000
+++ b/resources/themes/bootstrap/3/wt.css	2014-12-30 09:42:15.000000000 +0000
@@ -320,6 +320,31 @@
 }
 
 /*
+ * WTimeEdit
+ */
+
+.Wt-timeedit {
+  padding-right: 40px;
+  background-image: url('time-edit-button.png');
+  background-position: right top;
+  background-repeat: no-repeat;
+}
+
+.Wt-timeedit:hover {
+  cursor: default;
+  background-position: right -34px;
+}
+
+.Wt-timeedit:active {
+  cursor: default;
+  background-position: right -68px;
+}
+
+.Wt-timeedit::-ms-clear {
+  display: none;
+}
+
+/*
  * WDatePicker
  */
 
diff -Naur a/resources/themes/bootstrap/3/wt.less b/resources/themes/bootstrap/3/wt.less
--- a/resources/themes/bootstrap/3/wt.less	2014-03-28 10:41:53.000000000 +0000
+++ b/resources/themes/bootstrap/3/wt.less	2015-01-04 07:18:00.000000000 +0000
@@ -305,6 +305,30 @@
 }
 
 /*
+ * WTimeEdit
+ */
+.Wt-timeedit {
+    background-image: url('date-edit-button.png');
+    background-repeat: no-repeat;
+    background-position: right top;
+    padding-right: 40px;
+
+    &:hover {
+        cursor: default;
+        background-position: right -34px;
+    }
+
+    &:active {
+        cursor: default;
+        background-position: right -68px;
+    }
+}
+
+.Wt-timeedit::-ms-clear {
+    display:none
+}
+
+/*
  * WDatePicker
  */
 .Wt-datepicker input {
diff -Naur a/src/CMakeLists.txt b/src/CMakeLists.txt
--- a/src/CMakeLists.txt	2014-02-20 07:29:19.000000000 +0000
+++ b/src/CMakeLists.txt	2015-01-04 13:40:05.000000000 +0000
@@ -12,13 +12,13 @@
 
 MACRO (FILE_TO_STRING infile outfile var)
 ADD_CUSTOM_COMMAND(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${outfile}
-	           COMMAND ${CMAKE_COMMAND}
+               COMMAND ${CMAKE_COMMAND}
                    ARGS -Dinfile:FILEPATH=${CMAKE_CURRENT_SOURCE_DIR}/${infile}
                         -Doutfile:FILEPATH=${CMAKE_CURRENT_BINARY_DIR}/${outfile}
                         -Dvar:STRING=${var}
                         -P ${CMAKE_CURRENT_SOURCE_DIR}/filetostring.cmake
-		   MAIN_DEPENDENCY
-		        ${CMAKE_CURRENT_SOURCE_DIR}/${infile}
+           MAIN_DEPENDENCY
+                ${CMAKE_CURRENT_SOURCE_DIR}/${infile}
 )
 ENDMACRO (FILE_TO_STRING)
 
@@ -43,11 +43,11 @@
 FILE_TO_STRING(xml/wt.xml Wt_xml.C Wt_xml)
 FILE_TO_STRING(xml/auth_strings.xml AuthStrings_xml.C AuthStrings_xml)
 FILE_TO_STRING(xml/bootstrap_theme.xml
-	BootstrapTheme_xml.C BootstrapTheme_xml)
+    BootstrapTheme_xml.C BootstrapTheme_xml)
 FILE_TO_STRING(xml/bootstrap3_theme.xml
-	Bootstrap3Theme_xml.C Bootstrap3Theme_xml)
+    Bootstrap3Theme_xml.C Bootstrap3Theme_xml)
 FILE_TO_STRING(xml/auth_css_theme.xml
-	AuthCssTheme_xml.C AuthCssTheme_xml)
+    AuthCssTheme_xml.C AuthCssTheme_xml)
 
 SET(libsources
 Wt/PopupWindow.C
@@ -221,8 +221,11 @@
 Wt/WTextEdit.C
 Wt/WTheme.C
 Wt/WTime.C
+Wt/WTimeEdit.C
 Wt/WTimer.C
 Wt/WTimerWidget.C
+Wt/WTimePicker.C
+Wt/WTimeValidator.C
 Wt/WToolBar.C
 Wt/WTransform.C
 Wt/WTree.C
diff -Naur a/src/js/WTimeEdit.js b/src/js/WTimeEdit.js
--- a/src/js/WTimeEdit.js	1970-01-01 01:00:00.000000000 +0100
+++ b/src/js/WTimeEdit.js	2014-12-30 09:31:02.000000000 +0000
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2010 Emweb bvba, Kessel-Lo, Belgium.
+ *
+ * See the LICENSE file for terms of use.
+ */
+
+/* Note: this is at the same time valid JavaScript and C++. */
+
+WT_DECLARE_WT_MEMBER
+(1, JavaScriptConstructor, "WTimeEdit",
+ function(APP, edit, popup) {
+   /** @const */ var CLASS_HOVER = 'hover';
+   /** @const */ var CLASS_ACTIVE = 'active';
+   /** @const */ var CLASS_UNSELECTABLE = 'unselectable';
+
+   jQuery.data(edit, 'dobj', this);
+
+   var self = this, WT = APP.WT, $edit = $(edit);
+
+   function isReadOnly() {
+     return !!edit.getAttribute("readonly");
+   }
+
+   function isPopupVisible() {
+     return popup.style.display === '';
+   }
+
+   function getPopup() {
+     var p = $('#' + popup.id).get(0);
+     return jQuery.data(p, 'popup');
+   }
+
+   function resetButton() {
+     $edit.removeClass(CLASS_ACTIVE);
+   }
+
+   function showPopup() {
+     var p = getPopup();
+     p.bindHide(resetButton);
+     p.show(edit, WT.Vertical);
+   }
+
+   this.mouseOut = function(o, event) {
+     $edit.removeClass(CLASS_HOVER);
+   };
+
+   this.mouseMove = function(o, event) {
+     if (isReadOnly())
+       return;
+
+     var xy = WT.widgetCoordinates(edit, event);
+
+     if (xy.x > edit.offsetWidth - 40) {
+       $edit.addClass(CLASS_HOVER);
+     } else {
+       if ($edit.hasClass(CLASS_HOVER))
+     $edit.removeClass(CLASS_HOVER);
+     }
+   };
+
+   this.mouseDown = function(o, event) {
+     if (isReadOnly())
+       return;
+
+     var xy = WT.widgetCoordinates(edit, event);
+     if (xy.x > edit.offsetWidth - 40) {
+       $edit.addClass(CLASS_UNSELECTABLE).addClass(CLASS_ACTIVE);
+     }
+   };
+
+   this.mouseUp = function(o, event) {
+     $edit.removeClass(CLASS_UNSELECTABLE);
+
+     var xy = WT.widgetCoordinates(edit, event);
+     if (xy.x > edit.offsetWidth - 40)
+       showPopup();
+   };
+ });
diff -Naur a/src/js/WTimeEdit.min.js b/src/js/WTimeEdit.min.js
--- a/src/js/WTimeEdit.min.js	1970-01-01 01:00:00.000000000 +0100
+++ b/src/js/WTimeEdit.min.js	2014-12-30 09:31:07.000000000 +0000
@@ -0,0 +1,2 @@
+WT_DECLARE_WT_MEMBER(1,JavaScriptConstructor,"WTimeEdit",function(g,a,h){function f(){return!!a.getAttribute("readonly")}function i(){var b=$("#"+h.id).get(0);return jQuery.data(b,"popup")}function j(){c.removeClass("active")}function k(){var b=i();b.bindHide(j);b.show(a,e.Vertical)}jQuery.data(a,"dobj",this);var e=g.WT,c=$(a);this.mouseOut=function(){c.removeClass("hover")};this.mouseMove=function(b,d){if(!f())if(e.widgetCoordinates(a,d).x>a.offsetWidth-40)c.addClass("hover");else c.hasClass("hover")&&
+c.removeClass("hover")};this.mouseDown=function(b,d){f()||e.widgetCoordinates(a,d).x>a.offsetWidth-40&&c.addClass("unselectable").addClass("active")};this.mouseUp=function(b,d){c.removeClass("unselectable");e.widgetCoordinates(a,d).x>a.offsetWidth-40&&k()}});
diff -Naur a/src/js/WTimeValidator.js b/src/js/WTimeValidator.js
--- a/src/js/WTimeValidator.js	1970-01-01 01:00:00.000000000 +0100
+++ b/src/js/WTimeValidator.js	2015-01-04 13:40:05.000000000 +0000
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2011 Emweb bvba, Kessel-Lo, Belgium.
+ *
+ * See the LICENSE file for terms of use.
+ */
+
+/* Note: this is at the same time valid JavaScript and C++. */
+
+/*
+ * We could merge this with WIntValidator ...
+ */
+
+WT_DECLARE_WT_MEMBER
+(1, JavaScriptConstructor, "WTimeValidator",
+   function(mandatory, formats, bottom, top, blankError,
+        formatError) {
+
+   this.validate = function(text) {
+     if (text.length == 0)
+       if (mandatory)
+       return { valid: false, message: blankError };
+     else
+       return { valid: true };
+
+     var results;
+
+     var s = -1, m = -1, h = -1;
+
+     for (var i = 0, il = formats.length; i < il; ++i) {
+       var f = formats[i];
+       var r = new RegExp("^" + f.regexp + "$");
+
+       results = r.exec(text);
+       if (results != null) {
+     //s = f.getSecond(results);
+     m = f.getMinute(results);
+     h = f.getHour(results);
+
+     break;
+       }
+     }
+
+     if (results == null)
+       return { valid: false, message: formatError };
+
+     if( /*(m < 0) || (m > 59) ||*/ (m < 0) || (m > 59) || (h < 0) || (h > 23))
+         return {valid: false, message: formatError };
+
+     var dt = new Date(0, 0, 0, h, m, 0, 0);
+
+     if(/*dt.getSeconds() != s ||*/ dt.getMinutes() != m || dt.getHours() != h)
+     {
+         return {valid: false, message: formatError };
+     }
+
+     /*
+     if (bottom)
+       if (dt.getTime() < bottom.getTime())
+     return { valid: false, message: tooSmallError};
+
+     if (top)
+       if (dt.getTime() > top.getTime())
+     return { valid:false, message: tooLargeError};
+
+     */
+
+     return { valid: true };
+   };
+
+   });
diff -Naur a/src/js/WTimeValidator.min.js b/src/js/WTimeValidator.min.js
--- a/src/js/WTimeValidator.min.js	1970-01-01 01:00:00.000000000 +0100
+++ b/src/js/WTimeValidator.min.js	2015-01-04 13:40:05.000000000 +0000
@@ -0,0 +1,2 @@
+WT_DECLARE_WT_MEMBER(1,JavaScriptConstructor,"WDateValidator",function(l,i,j,k,m,f,n,o){this.validate=function(a){if(a.length==0)return l?{valid:false,message:m}:{valid:true};for(var b,c=-1,d=-1,g=-1,h=0,p=i.length;h<p;++h){var e=i[h];b=(new RegExp("^"+e.regexp+"$")).exec(a);if(b!=null){c=e.getMonth(b);d=e.getDay(b);g=e.getYear(b);break}}if(b==null)return{valid:false,message:f};if(d<=0||d>31||c<=0||c>12)return{valid:false,message:f};a=new Date(g,c-1,d);if(a.getDate()!=d||a.getMonth()!=c-1||a.getFullYear()!=
+g||a.getFullYear()<1400)return{valid:false,message:f};if(j)if(a.getTime()<j.getTime())return{valid:false,message:n};if(k)if(a.getTime()>k.getTime())return{valid:false,message:o};return{valid:true}}});
diff -Naur a/src/Wt/WBootstrapTheme.C b/src/Wt/WBootstrapTheme.C
--- a/src/Wt/WBootstrapTheme.C	2014-04-29 21:55:27.000000000 +0100
+++ b/src/Wt/WBootstrapTheme.C	2015-01-04 07:17:19.000000000 +0000
@@ -13,6 +13,7 @@
 #include "Wt/WComboBox"
 #include "Wt/WDateEdit"
 #include "Wt/WDatePicker"
+#include "Wt/WTimeEdit"
 #include "Wt/WDialog"
 #include "Wt/WEnvironment"
 #include "Wt/WGoogleMap"
@@ -151,6 +152,10 @@
     child->addStyleClass("Wt-datepicker");
     break;
 
+  case TimePickerPopupRole:
+    child->addStyleClass("Wt-timepicker");
+    break;
+
   case PanelTitleBarRole:
     child->addStyleClass(classAccordionHeading());
     break;
@@ -381,6 +386,11 @@
         return;
       }
 
+      WTimeEdit *timeEdit = dynamic_cast<WTimeEdit *>(widget);
+      if (timeEdit) {
+        element.addPropertyWord(PropertyClass, "Wt-timeedit");
+        return;
+      }
 
     }
     break;
diff -Naur a/src/Wt/WLocale b/src/Wt/WLocale
--- a/src/Wt/WLocale	2014-02-25 22:12:04.000000000 +0000
+++ b/src/Wt/WLocale	2015-01-04 13:40:05.000000000 +0000
@@ -136,6 +136,21 @@
    */
   WT_USTRING dateFormat() const { return dateFormat_; }
 
+  /*! \brief Sets the time format.
+   *
+   *  Sets the default format for time entry, eg. as used in
+   *  WTimeValidator. See WTime::toString() for the supported syntax.
+   *
+   *  The default time format is "HH:mm:ss".
+   */
+  void setTimeFormat(const WT_USTRING &format);
+
+  /*! \brief Returns the time format.
+   *
+   * Returns the time format.
+   */
+  WT_USTRING timeFormat() const { return timeFormat_; }
+
   /*! \brief Sets the date/time format.
    *
    * Sets the format for a localized time stamp (using
@@ -161,7 +176,7 @@
 
   /*! \brief Casts to the locale string (for pre-3.3.0 compatibility).
    *
-   * \deprecated Use name() instead. 
+   * \deprecated Use name() instead.
    */
   operator std::string() const { return name(); }
 
@@ -213,7 +228,7 @@
 private:
   std::string name_;
   WT_UCHAR decimalPoint_, groupSeparator_;
-  WT_USTRING dateFormat_, dateTimeFormat_;
+  WT_USTRING dateFormat_, timeFormat_, dateTimeFormat_;
 
   boost::local_time::time_zone_ptr time_zone_;
 
diff -Naur a/src/Wt/WLocale.C b/src/Wt/WLocale.C
--- a/src/Wt/WLocale.C	2014-03-07 11:21:38.000000000 +0000
+++ b/src/Wt/WLocale.C	2015-01-04 13:40:05.000000000 +0000
@@ -16,6 +16,7 @@
   : decimalPoint_("."),
     groupSeparator_(""),
     dateFormat_("yyyy-MM-dd"),
+    timeFormat_("HH:mm:ss"),
     dateTimeFormat_("yyyy-MM-dd HH:mm:ss")
 { }
 
@@ -24,6 +25,7 @@
     decimalPoint_(other.decimalPoint()),
     groupSeparator_(other.groupSeparator()),
     dateFormat_(other.dateFormat()),
+    timeFormat_(other.timeFormat()),
     dateTimeFormat_(other.dateTimeFormat()),
     time_zone_(other.time_zone_)
 { }
@@ -33,6 +35,7 @@
     decimalPoint_(systemLocale.decimalPoint()),
     groupSeparator_(systemLocale.groupSeparator()),
     dateFormat_(systemLocale.dateFormat()),
+    timeFormat_(systemLocale.timeFormat()),
     dateTimeFormat_(systemLocale.dateTimeFormat()),
     time_zone_(systemLocale.time_zone_)
 { }
@@ -42,6 +45,7 @@
     decimalPoint_(systemLocale.decimalPoint()),
     groupSeparator_(systemLocale.groupSeparator()),
     dateFormat_(systemLocale.dateFormat()),
+    timeFormat_(systemLocale.timeFormat()),
     dateTimeFormat_(systemLocale.dateTimeFormat()),
     time_zone_(systemLocale.time_zone_)
 { }
@@ -61,6 +65,11 @@
   dateFormat_ = format;
 }
 
+void WLocale::setTimeFormat(const WT_USTRING &format)
+{
+  timeFormat_ = format;
+}
+
 void WLocale::setTimeZone(const std::string& posixTimeZone)
 {
   time_zone_.reset(new boost::local_time::posix_time_zone(posixTimeZone));
diff -Naur a/src/Wt/WTheme b/src/Wt/WTheme
--- a/src/Wt/WTheme	2014-04-29 21:55:27.000000000 +0100
+++ b/src/Wt/WTheme	2015-01-04 07:17:19.000000000 +0000
@@ -35,6 +35,7 @@
   TableViewRowContainerRole = 300,
 
   DatePickerPopupRole = 400,
+  TimePickerPopupRole = 410,
 
   PanelTitleBarRole = 500,
   PanelCollapseButtonRole = 501,
@@ -179,8 +180,8 @@
   /*! \brief Applies a style that indicates the result of validation.
    */
   virtual void applyValidationStyle(WWidget *widget,
-				    const Wt::WValidator::Result& validation,
-				    WFlags<ValidationStyleFlag> flags) const
+                    const Wt::WValidator::Result& validation,
+                    WFlags<ValidationStyleFlag> flags) const
     = 0;
 
   virtual bool canBorderBoxElement(const DomElement& element) const = 0;
diff -Naur a/src/Wt/WTime.C b/src/Wt/WTime.C
--- a/src/Wt/WTime.C	2014-04-29 21:55:27.000000000 +0100
+++ b/src/Wt/WTime.C	2015-01-04 13:40:05.000000000 +0000
@@ -474,4 +474,10 @@
     time_(t)
 { }
 
+WTime::RegExpInfo WTime::formatToRegExp(const WT_USTRING &format)
+{
+    RegExpInfo result;
+    return result;
+}
+
 }
diff -Naur a/src/Wt/WTimeEdit b/src/Wt/WTimeEdit
--- a/src/Wt/WTimeEdit	1970-01-01 01:00:00.000000000 +0100
+++ b/src/Wt/WTimeEdit	2015-01-04 13:50:33.000000000 +0000
@@ -0,0 +1,75 @@
+
+#ifndef WTIME_EDIT_H_
+#define WTIME_EDIT_H_
+
+#include <Wt/WLineEdit>
+#include <Wt/WTime>
+#include <Wt/WTimeValidator>
+#include <Wt/WTimePicker>
+
+namespace Wt {
+
+class WT_API WTimeEdit : public WLineEdit
+{
+public:
+    /*! \brief Creates a new time edit.
+     */
+    WTimeEdit(WContainerWidget *parent = 0);
+
+    /*! \brief Sets the time
+     *
+     *  Does nothing if the current time is \p Null.
+     *
+     * \sa time()
+     */
+    void setTime(const WTime& time);
+
+    /*! \brief Returns the time.
+     *
+     * Returns an invalid time (for which WTime::isValid() returns
+     * \c false) if the time coult not be parsed using the current format(). <br>
+     *
+     * \sa setTime(), WTime::fromString(), WLineEdit::text()
+     */
+    WTime time() const;
+
+    virtual WTimeValidator *validator() const;
+
+    void setFormat(const WT_USTRING& format);
+
+    WT_USTRING format() const;
+
+    virtual void setHidden(bool hidden, const WAnimation& animation = WAnimation());
+
+    int minuteStep() const { return timePicker_->minuteStep(); }
+    void setMinuteStep(int step) { timePicker_->setMinuteStep(step); }
+
+protected:
+
+    virtual void render(WFlags<RenderFlag> flags);
+    virtual void propagateSetEnabled(bool enabled);
+
+    /*! \brief Sets the value from the time scroller to the line edit.
+     */
+    virtual void setFromTimePicker();
+
+    /*! \brief Sets the value from the line edit to the time scroller.
+     */
+    virtual void setFromLineEdit();
+
+    virtual void setText(const WString &text) { WLineEdit::setText(text); }
+
+
+private:
+
+    WPopupWidget *popup_;
+    WTimePicker *timePicker_;
+
+    void defineJavaScript();
+    void connectJavaScript(Wt::EventSignalBase& s, const std::string& methodName);
+
+};
+
+}
+
+#endif // WTIME_EDIT_H_
diff -Naur a/src/Wt/WTimeEdit.C b/src/Wt/WTimeEdit.C
--- a/src/Wt/WTimeEdit.C	1970-01-01 01:00:00.000000000 +0100
+++ b/src/Wt/WTimeEdit.C	2015-01-04 13:50:33.000000000 +0000
@@ -0,0 +1,168 @@
+
+#include "Wt/WTimeEdit"
+
+#include "Wt/WApplication"
+#include "Wt/WContainerWidget"
+#include "Wt/WLineEdit"
+#include "Wt/WLogger"
+#include "Wt/WPopupWidget"
+#include "Wt/WPushButton"
+#include "Wt/WTemplate"
+#include "Wt/WTheme"
+
+#include "WebUtils.h"
+
+#ifndef WT_DEBUG_JS
+#include "js/WTimeEdit.min.js"
+#endif
+
+namespace Wt {
+
+LOGGER("WTimeEdit");
+
+WTimeEdit::WTimeEdit(WContainerWidget *parent)
+    : WLineEdit(parent)
+{
+    changed().connect(this, &WTimeEdit::setFromLineEdit);
+
+    const char *TEMPLATE = "${timePicker}";
+
+    WTemplate *t = new WTemplate(WString::fromUTF8(TEMPLATE));
+    popup_ = new WPopupWidget(t, this);
+    popup_->setAnchorWidget(this);
+    popup_->setTransient(true, 2);
+
+    timePicker_ = new WTimePicker();
+    timePicker_->selectionChanged().connect(this, &WTimeEdit::setFromTimePicker);
+
+    t->bindWidget("timePicker", timePicker_);
+
+    WApplication::instance()->theme()->apply(this, popup_, TimePickerPopupRole);
+
+    escapePressed().connect(popup_, &WPopupWidget::hide);
+    escapePressed().connect(this, &WTimeEdit::setFocus);
+
+    setValidator(new WTimeValidator("HH:mm", this));
+
+}
+
+void WTimeEdit::setTime(const WTime& time)
+{
+    setText(time.toString(format()));
+
+    timePicker_->setTime(time);
+}
+
+WTime WTimeEdit::time() const
+{
+    return WTime::fromString(text(), format());
+}
+
+WTimeValidator *WTimeEdit::validator() const
+{
+    return dynamic_cast<WTimeValidator *>(WLineEdit::validator());
+}
+
+void WTimeEdit::setFormat(const WT_USTRING& format)
+{
+    WTimeValidator *tv = validator();
+
+    if(tv)
+    {
+        WTime t = this->time();
+        tv->setFormat(format);
+        setTime(t);
+    }
+    else
+    {
+        LOG_WARN("setFormaT() ignored since validator is not WTimeValidator");
+    }
+}
+
+WT_USTRING WTimeEdit::format() const
+{
+    WTimeValidator *tv = validator();
+
+    if(tv)
+    {
+        return tv->format();
+    }
+    else
+    {
+        LOG_WARN("format() is bogus since validator is not WTimeValidator.");
+        return WT_USTRING();
+    }
+}
+
+void WTimeEdit::setHidden(bool hidden, const WAnimation& animation)
+{
+    WLineEdit::setHidden(hidden, animation);
+
+    popup_->setHidden(hidden, animation);
+}
+
+void WTimeEdit::render(WFlags<RenderFlag> flags)
+{
+    if(flags & RenderFull) {
+        defineJavaScript();
+    }
+
+    WLineEdit::render(flags);
+}
+
+void WTimeEdit::propagateSetEnabled(bool enabled)
+{
+    WLineEdit::propagateSetEnabled(enabled);
+}
+
+void WTimeEdit::setFromTimePicker()
+{
+    setTime(timePicker_->time());
+}
+
+void WTimeEdit::setFromLineEdit()
+{
+    WTime t = WTime::fromString(text(), format());
+
+    if(t.isValid())
+    {
+        timePicker_->setTime(t);
+    }
+}
+
+void WTimeEdit::defineJavaScript()
+{
+    WApplication *app = WApplication::instance();
+
+    LOAD_JAVASCRIPT(app, "js/WTimeEdit.js", "WTimeEdit", wtjs1);
+
+    std::string jsObj = "new " WT_CLASS ".WTimeEdit("
+            + app->javaScriptClass() + "," + jsRef() + ","
+            + popup_->jsRef() + ");";
+
+    setJavaScriptMember(" WTimeEdit", jsObj);
+
+#ifdef WT_CNOR
+    EventSignalBase& b = mouseMoved();
+    EventSignalBase& c = keyWentDown();
+#endif
+
+    connectJavaScript(mouseMoved(), "mouseMove");
+    connectJavaScript(mouseWentUp(), "mouseUp");
+    connectJavaScript(mouseWentDown(), "mouseDown");
+    connectJavaScript(mouseWentOut(), "mouseOut");
+
+}
+
+void WTimeEdit::connectJavaScript(Wt::EventSignalBase& s, const std::string& methodName)
+{
+    std::string jsFunction =
+            "function(dobj, event) {"
+            """var o = jQuery.data(" + jsRef() + ", 'dobj');"
+            """if(o) o." + methodName + "(dobj, event);"
+            "}";
+
+    s.connect(jsFunction);
+}
+
+}
diff -Naur a/src/Wt/WTimePicker b/src/Wt/WTimePicker
--- a/src/Wt/WTimePicker	1970-01-01 01:00:00.000000000 +0100
+++ b/src/Wt/WTimePicker	2014-12-31 10:08:39.000000000 +0000
@@ -0,0 +1,46 @@
+
+#ifndef WTIME_PICKER_H_
+#define WTIME_PICKER_H_
+
+#include "WCompositeWidget"
+#include "WContainerWidget"
+#include "WTime"
+
+namespace Wt {
+
+class WT_API WTimePicker : public WCompositeWidget
+{
+public:
+    WTimePicker(WContainerWidget *parent = 0);
+    WTimePicker(const WTime &time, WContainerWidget *parent = 0);
+
+    WTime time();
+    void setTime(const WTime& time);
+
+    int minuteStep() const { return minuteStep_; }
+    void setMinuteStep(int step) { minuteStep_ = step; }
+
+    Signal<>& selectionChanged() { return selectionChanged_; }
+
+private:
+
+    int minuteStep_;
+
+    void init(const WTime &time = WTime());
+
+    WText *hourText_;
+    WText *minuteText_;
+
+    void incrementMinutes();
+    void decrementMinutes();
+    void incrementHours();
+    void decrementHours();
+
+    Signal<> selectionChanged_;
+
+};
+
+
+} // end namespace Wt
+
+#endif // WTIME_PICKER_H_
diff -Naur a/src/Wt/WTimePicker.C b/src/Wt/WTimePicker.C
--- a/src/Wt/WTimePicker.C	1970-01-01 01:00:00.000000000 +0100
+++ b/src/Wt/WTimePicker.C	2015-01-04 13:40:05.000000000 +0000
@@ -0,0 +1,288 @@
+
+#include <boost/format.hpp>
+
+#include "WTimePicker"
+
+#include "WStringStream"
+#include "WTemplate"
+#include "WPushButton"
+#include "WText"
+
+namespace Wt {
+
+WTimePicker::WTimePicker(WContainerWidget *parent)
+    : WCompositeWidget(parent), minuteStep_(1),
+      selectionChanged_(this)
+{
+    init();
+}
+
+WTimePicker::WTimePicker(const WTime &time, WContainerWidget *parent)
+    : WCompositeWidget(parent), minuteStep_(1)
+{
+    init(time);
+}
+
+void WTimePicker::init(const WTime &time)
+{
+    Wt::WStringStream text;
+    text << "<table>"
+            """<tr>"
+            ""  "<th>${incrementHour}</th>"
+            ""  "<th></th>"
+            ""  "<th>${incrementMinute}</th>"
+            """</tr>"
+            """<tr style=\"height: 20px;\">"
+            ""  "<td valign=\"middle\" align=\"center\">${hourText}</td>"
+            ""  "<td valign=\"middle\" align=\"center\">:</td>"
+            ""  "<td valign=\"middle\" align=\"center\">${minuteText}</td>"
+            """</tr>"
+            """<tr>"
+            ""  "<th>${decrementHour}</th>"
+            ""  "<th></th>"
+            ""  "<th>${decrementMinute}</th>"
+            """</tr>"
+            "</table>";
+
+    WTemplate *impl = new WTemplate();
+    setImplementation(impl);
+    impl->setTemplateText(WString::fromUTF8(text.str(), XHTMLUnsafeText));
+
+    WPushButton *incHourButton = new WPushButton();
+    incHourButton->setIcon("resources/themes/bootstrap/3/glyphicons_up.png");
+
+    WPushButton *decHourButton = new WPushButton();
+    decHourButton->setIcon("resources/themes/bootstrap/3/glyphicons_down.png");
+
+    WPushButton *incMinuteButton = new WPushButton();
+    incMinuteButton->setIcon("resources/themes/bootstrap/3/glyphicons_up.png");
+
+    WPushButton *decMinuteButton = new WPushButton();
+    decMinuteButton->setIcon("resources/themes/bootstrap/3/glyphicons_down.png");
+
+    hourText_ = new WText("0");
+    hourText_->setInline(false);
+    hourText_->setTextAlignment(AlignCenter);
+
+    minuteText_ = new WText("00");
+    minuteText_->setInline(false);
+    minuteText_->setTextAlignment(AlignCenter);
+
+    impl->bindWidget("incrementHour", incHourButton);
+    impl->bindWidget("decrementHour", decHourButton);
+
+    impl->bindWidget("hourText", hourText_);
+    impl->bindWidget("minuteText", minuteText_);
+
+    impl->bindWidget("incrementMinute", incMinuteButton);
+    impl->bindWidget("decrementMinute", decMinuteButton);
+
+    incHourButton->clicked().connect(this, &WTimePicker::incrementHours);
+    decHourButton->clicked().connect(this, &WTimePicker::decrementHours);
+
+    incMinuteButton->clicked().connect(this, &WTimePicker::incrementMinutes);
+    decMinuteButton->clicked().connect(this, &WTimePicker::decrementMinutes);
+
+}
+
+WTime WTimePicker::time()
+{
+    int hours = 0, minutes = 0;
+
+    try
+    {
+        hours = boost::lexical_cast<int>(hourText_->text().toUTF8());
+        minutes = boost::lexical_cast<int>(minuteText_->text().toUTF8());
+    }
+    catch(const boost::bad_lexical_cast& ex)
+    {
+        Wt::log("Error") << "boost::bad_lexical_cast caught in " << __FUNCTION__ << " (" << __FILE__ << ":" << __LINE__ << "): " << ex.what();
+    }
+
+    return WTime(hours, minutes);
+}
+
+void WTimePicker::setTime(const WTime& time)
+{
+
+    if(!time.isValid())
+    {
+        Wt::log("error") << "Time is invalid!";
+        return;
+    }
+
+    std::string hoursStr("0"), minutesStr("00");
+
+    try
+    {
+        hoursStr = time.toString("hh").toUTF8();
+
+        minutesStr = time.toString("mm").toUTF8();
+    }
+    catch(const boost::bad_lexical_cast& ex)
+    {
+        Wt::log("Error") << "boost::bad_lexical_cast caught in " << __FUNCTION__ << " (" << __FILE__ << ":" << __LINE__ << "): " << ex.what();
+    }
+
+    hourText_->setText(hoursStr);
+    minuteText_->setText(minutesStr);
+}
+
+void WTimePicker::incrementMinutes()
+{
+    std::string str = minuteText_->text().toUTF8();
+    int curVal = 0;
+    if(!str.empty())
+    {
+        try
+        {
+            curVal = boost::lexical_cast<int>(str);
+        }
+        catch(const boost::bad_lexical_cast& ex)
+        {
+            Wt::log("Error") << "boost::bad_lexical_cast caught in " << __FUNCTION__ << " (" << __FILE__ << ":" << __LINE__ << "): " << ex.what();
+        }
+    }
+
+    if((curVal += minuteStep_) >= 60)
+    {
+        curVal -= 60;
+    }
+
+    try
+    {
+        std::stringstream ss;
+        ss << std::setw(2) << std::setfill('0') <<  curVal;
+        str = ss.str();
+    }
+    catch(const boost::bad_lexical_cast& ex)
+    {
+        Wt::log("Error") << "boost::bad_lexical_cast caught in " << __FUNCTION__ << " (" << __FILE__ << ":" << __LINE__ << "): " << ex.what();
+    }
+
+    minuteText_->setText(str);
+
+    selectionChanged_.emit();
+}
+
+void WTimePicker::decrementMinutes()
+{
+    std::string str = minuteText_->text().toUTF8();
+    int curVal = 0;
+    if(!str.empty())
+    {
+        try
+        {
+            curVal = boost::lexical_cast<int>(str);
+        }
+        catch(const boost::bad_lexical_cast& ex)
+        {
+            Wt::log("Error") << "boost::bad_lexical_cast caught in " << __FUNCTION__ << " (" << __FILE__ << ":" << __LINE__ << "): " << ex.what();
+        }
+    }
+
+    if((curVal -= minuteStep_) < 0)
+    {
+        curVal += 60;
+    }
+
+    try
+    {
+        std::stringstream ss;
+        ss << std::setw(2) << std::setfill('0') <<  curVal;
+        str = ss.str();
+    }
+    catch(const boost::bad_lexical_cast& ex)
+    {
+        Wt::log("Error") << "boost::bad_lexical_cast caught in " << __FUNCTION__ << " (" << __FILE__ << ":" << __LINE__ << "): " << ex.what();
+    }
+
+    minuteText_->setText(str);
+
+    selectionChanged_.emit();
+}
+
+void WTimePicker::incrementHours()
+{
+    std::string str = hourText_->text().toUTF8();
+    int curVal = 0;
+    if(!str.empty())
+    {
+        try
+        {
+            curVal = boost::lexical_cast<int>(str);
+        }
+        catch(const boost::bad_lexical_cast& ex)
+        {
+            Wt::log("Error") << "boost::bad_lexical_cast caught in " << __FUNCTION__ << " (" << __FILE__ << ":" << __LINE__ << "): " << ex.what();
+        }
+    }
+
+    if((curVal + 1) < 24)
+    {
+        curVal++;
+    }
+    else
+    {
+        curVal -= 23;
+    }
+
+    try
+    {
+        std::stringstream ss;
+        ss << std::setw(2) << std::setfill('0') <<  curVal;
+        str = ss.str();
+    }
+    catch(const boost::bad_lexical_cast& ex)
+    {
+        Wt::log("Error") << "boost::bad_lexical_cast caught in " << __FUNCTION__ << " (" << __FILE__ << ":" << __LINE__ << "): " << ex.what();
+    }
+
+
+    hourText_->setText(str);
+
+    selectionChanged_.emit();
+}
+
+void WTimePicker::decrementHours()
+{
+    std::string str = hourText_->text().toUTF8();
+    int curVal = 0;
+    if(!str.empty())
+    {
+        try
+        {
+            curVal = boost::lexical_cast<int>(str);
+        }
+        catch(const boost::bad_lexical_cast& ex)
+        {
+            Wt::log("Error") << "boost::bad_lexical_cast caught in " << __FUNCTION__ << " (" << __FILE__ << ":" << __LINE__ << "): " << ex.what();
+        }
+    }
+
+    if((curVal - 1) >= 0)
+    {
+        curVal--;
+    }
+    else
+    {
+        curVal += 23;
+    }
+
+    try
+    {
+        std::stringstream ss;
+        ss << std::setw(2) << std::setfill('0') <<  curVal;
+        str = ss.str();
+    }
+    catch(const boost::bad_lexical_cast& ex)
+    {
+        Wt::log("Error") << "boost::bad_lexical_cast caught in " << __FUNCTION__ << " (" << __FILE__ << ":" << __LINE__ << "): " << ex.what();
+    }
+
+    hourText_->setText(str);
+
+    selectionChanged_.emit();
+}
+
+} // end namespace Wt
diff -Naur a/src/Wt/WTimeValidator b/src/Wt/WTimeValidator
--- a/src/Wt/WTimeValidator	1970-01-01 01:00:00.000000000 +0100
+++ b/src/Wt/WTimeValidator	2015-01-04 13:40:05.000000000 +0000
@@ -0,0 +1,49 @@
+
+#ifndef WTIME_VALIDATOR_H_
+#define WTIME_VALIDATOR_H_
+
+#include "Wt/WValidator"
+
+namespace Wt {
+
+class WT_API WTimeValidator : public WValidator
+{
+public:
+    WTimeValidator(WObject *parent = 0);
+
+    WTimeValidator(const WT_USTRING &format, WObject *parent = 0);
+
+    void setFormat(const WT_USTRING &format);
+
+    virtual WT_USTRING format() const { return formats_[0]; }
+
+    /*! \brief Sets the time formats used to parse time strings.
+     */
+    void setFormats(const std::vector<WT_USTRING>& formats);
+
+    /*! \brief Returns the time formats used to parse time strings.
+     */
+    const std::vector<WT_USTRING>& formats() const { return formats_; }
+
+    virtual Result validate(const WT_USTRING &input) const;
+
+    void setInvalidNotATimeText(const WString &text);
+
+    WString invalidNotATimeText() const;
+
+    virtual std::string javaScriptValidate() const;
+
+
+private:
+
+    std::vector<WT_USTRING> formats_;
+
+    WString notATimeText_;
+
+    static void loadJavascript(WApplication *app);
+
+};
+
+}
+
+#endif // WTIME_VALIDATOR_H_
diff -Naur a/src/Wt/WTimeValidator.C b/src/Wt/WTimeValidator.C
--- a/src/Wt/WTimeValidator.C	1970-01-01 01:00:00.000000000 +0100
+++ b/src/Wt/WTimeValidator.C	2015-01-04 13:40:05.000000000 +0000
@@ -0,0 +1,141 @@
+
+#include "Wt/WApplication"
+
+#include "Wt/WTime"
+#include "Wt/WTimeValidator"
+#include "Wt/WLocale"
+#include "Wt/WLogger"
+#include "Wt/WStringStream"
+
+#ifndef WT_DEBUG_JS
+#include "js/WTimeValidator.min.js"
+#endif
+
+namespace Wt {
+
+LOGGER("WTimeValidator");
+
+WTimeValidator::WTimeValidator(WObject *parent)
+    : WValidator(parent)
+{
+    setFormat(WLocale::currentLocale().timeFormat());
+}
+
+WTimeValidator::WTimeValidator(const WString &format, WObject *parent)
+    : WValidator(parent)
+{
+    setFormat(format);
+}
+
+void WTimeValidator::setFormat(const WT_USTRING &format)
+{
+    if(formats_.empty() || formats_[0] != format) {
+        formats_.clear();
+        formats_.push_back(format);
+        repaint();
+    }
+}
+
+void WTimeValidator::setFormats(const std::vector<WT_USTRING>& formats)
+{
+    formats_ = formats;
+    repaint();
+}
+
+WValidator::Result WTimeValidator::validate(const WT_USTRING &input) const
+{
+    if(input.empty())
+    {
+        return WValidator::validate(input);
+    }
+
+    for(unsigned i = 0; i < formats_.size(); ++i)
+    {
+        try
+        {
+            WTime t = WTime::fromString(input, formats_[i]);
+
+            if(t.isValid())
+            {
+                return Result(Valid);
+            }
+        }
+        catch(std::exception &e)
+        {
+            LOG_WARN("validate(): " << e.what());
+        }
+    }
+
+    return Result(Invalid, invalidNotATimeText());
+}
+
+void WTimeValidator::setInvalidNotATimeText(const WString &text)
+{
+    notATimeText_ = text;
+}
+
+WString WTimeValidator::invalidNotATimeText() const
+{
+    if(!notATimeText_.empty())
+    {
+        WString s = notATimeText_;
+        s.arg(formats_[0]);
+        return s;
+    }
+    else
+    {
+        return WString::tr("Wt.WTimeValidator.WrongFormat").arg(formats_[0]);
+    }
+}
+
+std::string WTimeValidator::javaScriptValidate() const
+{
+    loadJavascript(WApplication::instance());
+
+    WStringStream js;
+
+    js << "new " WT_CLASS ".WTimeValidator("
+       << isMandatory()
+       << ", [";
+
+    for(unsigned i = 0; i < formats_.size(); ++i)
+    {
+        WTime::RegExpInfo r = WTime::formatToRegExp(formats_[i]);
+
+        if(i != 0)
+        {
+            js << ',';
+        }
+
+        js << "{"
+           << "regexp:" << WWebWidget::jsStringLiteral(r.regexp) << ','
+           << "getSecond:function(results){" << r.secGetJS << ";},"
+           << "getMinute:function(results){" << r.minuteGetJS << ";},"
+           << "getHour:function(results){" << r.hourGetJS << ";}"
+           << "}";
+    }
+
+    js << "],";
+
+    js << "null, ";
+
+    js << "null, ";
+
+    js << "," << invalidBlankText().jsStringLiteral()
+       << "," << invalidNotATimeText().jsStringLiteral()
+       << ");";
+
+    //return js.str();
+    return std::string();
+}
+
+void WTimeValidator::loadJavascript(WApplication *app)
+{
+    LOAD_JAVASCRIPT(app, "js/WTimeValidator.js", "WTimeValidator", wtjs1);
+
+
+
+
+}
+
+}
