Click here to Skip to main content
Click here to Skip to main content

Date-Time Edit Control

By , 25 Mar 2000
 

Sample Image - datetimedit.jpg

Introduction

Many articles about the masked edit control could be discovered from MFC sites. Their Date Time Edit controls show some faults such as out-of-range values are not verified, slow processing, etc. Why? Maybe because they are too general and cover many things.

This VDateTimeEdit class based on MaskEdit's idea is very simple and understandable for any programmer. You can choose either numeric keys or up/down keys to change the date & time displayed. The Date-Time display is configurable by SetMask().

Mask convention:

DD Day hh Hour
MM Month mm minute
YYYY Year ss second

Enjoy yourself and please report me the bugs you have found.

Source Code

/////////////////////////////////////////////////////////////////////////////
// VDateTimeEdit window

const UINT WM_DATETIME_EDIT = ::RegisterWindowMessage("WM_DATETIME_EDIT");
#define DTE_DATETIMECHANGED 0x0001

class VDateTimeEdit : public CEdit
{
// Construction
public:
    VDateTimeEdit();

// Attributes
public:

// Operations
public:

// Overrides
    // ClassWizard generated virtual function overrides
    //{{AFX_VIRTUAL(VDateTimeEdit)
    protected:
    virtual void PreSubclassWindow();
    //}}AFX_VIRTUAL

// Implementation
public:
    CString GetMask() const {return m_strMask;}
    void SetMask(CString mask);
    COleDateTime GetDateTime();
    COleDateTime GetDateTime(CString str);
    void SetDateTime(COleDateTime dt);

    virtual ~VDateTimeEdit();

    // Generated message map functions
protected:
    afx_msg void OnContextMenu(CWnd*, CPoint point);

    CString m_strMask;
    //{{AFX_MSG(VDateTimeEdit)
    afx_msg void OnChar(UINT nChar, UINT nRepCnt, UINT nFlags);
    afx_msg void OnKeyUp(UINT nChar, UINT nRepCnt, UINT nFlags);
    afx_msg void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags);
    afx_msg void OnMouseMove(UINT nFlags, CPoint point);
    //}}AFX_MSG

    DECLARE_MESSAGE_MAP()
};


bool VAtoi(CString str, int &res)
{
    str.Remove(char(32));
    if (str.IsEmpty()) return false;
    bool error = false;
    for (int i = 0; i < str.GetLength() && !error; i++) {
        int c = ::tolower(str[i]);
        error = !(
            (c >= '0' && c <= '9')
            || ((c == '-' || c == '+') && i == 0)
            );
    }
    if (!error) res = atoi(LPCTSTR(str));
    return !error;
}

template<class TYPE>
void VSetInRange(TYPE &x, TYPE min, TYPE max) 
{
    ASSERT(min<=max);
    if (x < min) x = min; else if (x > max) x = max;
}

/************************** VDateTimeEdit **************************/

VDateTimeEdit::VDateTimeEdit()
{
    m_strMask = _T("DD/MM/YYYY hh:mm:ss");
}

VDateTimeEdit::~VDateTimeEdit()
{

}


BEGIN_MESSAGE_MAP(VDateTimeEdit, CEdit)
    ON_WM_CONTEXTMENU()
    //{{AFX_MSG_MAP(VDateTimeEdit)
    ON_WM_CHAR()
    ON_WM_KEYUP()
    ON_WM_KEYDOWN()
    ON_WM_MOUSEMOVE()
    //}}AFX_MSG_MAP
END_MESSAGE_MAP()

///////////////////////////////////////////////////////////////////////////
// VDateTimeEdit message handlers

void VDateTimeEdit::OnChar(UINT nChar, UINT /*nRepCnt*/, UINT /*nFlags*/) 
{
    CString old;
    GetWindowText(old);
    int pos;
    TCHAR c;
    GetSel(pos, pos);
    switch (nChar) {
        case VK_BACK: pos--;
            break;
        case VK_UP: pos--;
            break;
        case '0':case '1':case '2':case '3':
        case '4':case '5':case '6':case '7':
        case '8':case '9':
            if (pos < old.GetLength()
                  && pos < m_strMask.GetLength()
                  && (UINT)old[pos] != nChar
                  && ((c = m_strMask[pos]) == 'D' || 
                  c == 'M' || c == 'Y' || c == 'h' 
                  || c == 'm' || c == 's')) {
                CString str = old;
                str.SetAt(pos, (TCHAR)nChar);
                COleDateTime dt = GetDateTime(str);
                if (dt.GetStatus() == COleDateTime::valid) SetDateTime(dt);
            }
            if (++pos < m_strMask.GetLength())
                for ( c = m_strMask[pos]; pos < m_strMask.GetLength() 
                    && c != 'D' && 
                    c != 'M' && c != 'Y' && 
                    c != 'h' && c != 'm' && c != 's';
                  c = m_strMask[pos]) pos++;
            break;
        default:
            if (++pos < m_strMask.GetLength())
                for ( c = m_strMask[pos]; pos < m_strMask.GetLength() 
                      && c != 'D' && c != 'M' 
                      && c != 'Y' && c != 'h' 
                      && c != 'm' && c != 's';
                    c = m_strMask[pos]) pos++;
            break;
    }
    SetSel(pos, pos);
}

void VDateTimeEdit::OnKeyUp(UINT nChar, UINT nRepCnt, UINT nFlags) 
{
    CEdit::OnKeyUp(nChar, nRepCnt, nFlags);
}

void VDateTimeEdit::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) 
{
    switch (nChar) {
        case VK_DELETE:case VK_INSERT:
            break;
        case VK_UP: case VK_DOWN:
            {
                CString old;
                GetWindowText(old);
                int pos;
                GetSel(pos, pos);
                if (pos < m_strMask.GetLength()) {
                    TCHAR c = m_strMask[pos];
                    if (c != 'D' && c != 'M' && 
                      c != 'Y' && c != 'h' && 
                      c != 'm' && c != 's')
                        if (--pos >= 0) c = m_strMask[pos];
                    tm t;
                    COleDateTime dt = GetDateTime();
                    t.tm_mday = dt.GetDay();
                    t.tm_mon = dt.GetMonth();
                    t.tm_year = dt.GetYear();
                    t.tm_hour = dt.GetHour();
                    t.tm_min = dt.GetMinute();
                    t.tm_sec = dt.GetSecond();
                    switch (c) {
                        case 'D':
                            t.tm_mday += (nChar!=VK_UP) ? -1 : 1;
                            break;
                        case 'M':
                            t.tm_mon += (nChar!=VK_UP) ? -1 : 1;
                            break;
                        case 'Y':
                            t.tm_year += (nChar!=VK_UP) ? -1 : 1;
                            break;
                        case 'h':
                            t.tm_hour += (nChar!=VK_UP) ? -1 : 1;
                            break;
                        case 'm':
                            t.tm_min += (nChar!=VK_UP) ? -1 : 1;
                            break;
                        case 's':
                            t.tm_sec += (nChar!=VK_UP) ? -1 : 1;
                            break;
                        default    :
                             CEdit::OnKeyDown(nChar, nRepCnt, nFlags);
                            break;
                    }
                    dt.SetDateTime(t.tm_year, t.tm_mon, t.tm_mday, 
                                      t.tm_hour, t.tm_min, t.tm_sec);
                    if (dt.GetStatus() == COleDateTime::valid) 
                        SetDateTime(dt);
                } else CEdit::OnKeyDown(nChar, nRepCnt, nFlags);
            }
            break;
        default:
            CEdit::OnKeyDown(nChar, nRepCnt, nFlags);
            break;
    }
}

void VDateTimeEdit::OnMouseMove(UINT nFlags, CPoint point) 
{
    // TODO: Add your message handler code here and/or call default
    
    CEdit::OnMouseMove(nFlags, point);
}

void VDateTimeEdit::PreSubclassWindow() 
{
    SetDateTime(COleDateTime::GetCurrentTime());
    CEdit::PreSubclassWindow();
}

void VDateTimeEdit::SetDateTime(COleDateTime dt)
{
    CString str;
    int i, n = m_strMask.GetLength();
    for (i = 0; i < n; i++) {
        CString s;
        int val;
        TCHAR c = m_strMask[i];
        switch (c) {
            case 'D':case 'M':case 'Y':case 'h':case 'm':case 's':
                for (; i < n; i++) {
                    if (m_strMask[i] == c) s += c;
                    else {i--; break;}
                }
                break;
            default :
                break;
        }
        CString format;
        format.Format("%%0%dd", s.GetLength());
        switch (c) {
            case 'D':
                val = dt.GetDay();
                ::VSetInRange(val, 0, 31);
                s.Format(format, val);
                break;
            case 'M':
                val = dt.GetMonth();
                ::VSetInRange(val, 1, 12);
                s.Format(format, val);
                break;
            case 'Y':
                val = dt.GetYear();
                ::VSetInRange(val, 1900, 9990);
                s.Format(format, val);
                break;
            case 'h':
                val = dt.GetHour();
                ::VSetInRange(val, 0, 23);
                s.Format(format, val);
                break;
            case 'm':
                val = dt.GetMinute();
                ::VSetInRange(val, 0, 59);
                s.Format(format, val);
                break;
            case 's':
                val = dt.GetSecond();
                ::VSetInRange(val, 0, 59);
                s.Format(format, val);
                break;
            default :
                s = c;
                break;
        }
        str += s;
    }
    int pos;
    GetSel(pos, pos);
    SetWindowText(str);
    SetSel(pos, pos);
    CWnd *pOwner = GetParent();
    if (pOwner != NULL) 
      pOwner->PostMessage(WM_DATETIME_EDIT, 
                  (WPARAM)DTE_DATETIMECHANGED, (LPARAM)0);
}

COleDateTime VDateTimeEdit::GetDateTime()
{
    CString str;
    GetWindowText(str);
    return GetDateTime(str);
}

COleDateTime VDateTimeEdit::GetDateTime(CString str)
{
    tm t;
    COleDateTime dt;
    dt.SetStatus(COleDateTime::invalid);
    CTime::GetCurrentTime().GetLocalTm(&t);
    t.tm_mon += 1;
    t.tm_year += 1900;

    int i, n = m_strMask.GetLength();
    for (i = 0; i < n; i++) {
        CString s;
        TCHAR c = m_strMask[i];
        switch (c) {
            case 'D':case 'M':case 'Y':case 'h':case 'm':case 's':
                for ( ; i < n; i++) {
                    if (str.GetLength() <= s.GetLength()) break;
                    if (m_strMask[i] == c) s += str[s.GetLength()];
                    else {i--; break;}
                }
                break;
            default :
                if (str.GetLength() < 0 || str[0] != (s = c)) return dt;
                break;
        }
        str = str.Right(str.GetLength() - s.GetLength());
        int val;
        switch (c) {
            case 'D':
                if (!::VAtoi(s, val)) return dt;
                ::VSetInRange(val, 1, 31);
                t.tm_mday = val;
                break;
            case 'M':
                if (!::VAtoi(s, val)) return dt;
                ::VSetInRange(val, 1, 12);
                t.tm_mon = val;
                break;
            case 'Y':
                if (!::VAtoi(s, val)) return dt;
                ::VSetInRange(val, 1900, 9990);
                t.tm_year = val;
                break;
            case 'h':
                if (!::VAtoi(s, val)) return dt;
                ::VSetInRange(val, 0, 23);
                t.tm_hour = val;
                break;
            case 'm':
                if (!::VAtoi(s, val)) return dt;
                ::VSetInRange(val, 0, 59);
                t.tm_min = val;
                break;
            case 's':
                if (!::VAtoi(s, val)) return dt;
                ::VSetInRange(val, 0, 59);
                t.tm_sec = val;
                break;
            default :
                break;
        }
    }
    dt.SetStatus(COleDateTime::valid);
    dt.SetDateTime(t.tm_year, t.tm_mon, 
            t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec);
    return dt;
}

void VDateTimeEdit::OnContextMenu(CWnd*, CPoint)
{
}

void VDateTimeEdit::SetMask(CString mask) {
    COleDateTime time = GetDateTime();
    m_strMask = mask;
    SetDateTime(time);
}

That's it!

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

Tri VU KHAC
Belgium Belgium
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
Suggestionmy attempt using Allow Edit propertymemberzriniadhi3 Oct '11 - 7:44 
thanks for your article. my requirement was similar but smaller i guess, and i tried to use the Allow Edit property (DTN_APPCANPARSE), maybe microsoft added it recently, but here is my solution:
(the COleDateTime m_D is a quick fix data buffer, maybe it can be avoided!)
 
EditableDateTimeCtrl.h
 
#pragma once
#include "afxdtctl.h"
 
class CEditableDateTimeCtrl :
	public CDateTimeCtrl
{
public:
	CEditableDateTimeCtrl(void);
 
private:
	COleDateTime m_D; // temporary variable

protected:
	//{{AFX_MSG(CEditableDateTimeCtrl)
	//}}AFX_MSG
	afx_msg void OnUserString(NMHDR* pNMHDR, LRESULT* pResult);
	afx_msg void OnDatetimeChange(NMHDR* pNMHDR, LRESULT* pResult);
	DECLARE_MESSAGE_MAP()
 
};
 

 
EditableDateTimeCtrl.cpp:
 

#include "stdafx_ERFGUI.h"
#include "EditableDateTimeCtrl.h"
 
CEditableDateTimeCtrl::CEditableDateTimeCtrl(void)
{
	m_D.SetDate(1000, 1, 1); // an invalid value
}
 

BEGIN_MESSAGE_MAP(CEditableDateTimeCtrl, CDateTimeCtrl)
	//{{AFX_MSG_MAP(CEditableDateTimeCtrl)
	//}}AFX_MSG_MAP
	ON_NOTIFY_REFLECT(DTN_DATETIMECHANGE, OnDatetimeChange)
	ON_NOTIFY_REFLECT(DTN_USERSTRING, OnUserString)
END_MESSAGE_MAP()
 

void CEditableDateTimeCtrl::OnUserString(NMHDR *pNMHDR, LRESULT *pResult)
{
	LPNMDATETIMESTRING pDTString = reinterpret_cast<LPNMDATETIMESTRING>(pNMHDR);
	VERIFY(m_D.ParseDateTime(pDTString->pszUserString));
	*pResult = 0;
}
 
void CEditableDateTimeCtrl::OnDatetimeChange(NMHDR *pNMHDR, LRESULT *pResult)
{
	LPNMDATETIMECHANGE pDTChange = reinterpret_cast<LPNMDATETIMECHANGE>(pNMHDR);
		SetFormat(NULL);
	  if (m_D.GetYear() != 1000) {
		SetTime(m_D);
		m_D.SetDate(1000, 1, 1);
	  }
 
	*pResult = 0;
}

Generalproblem with functionalitymemberKipo Kiposki5 Apr '10 - 12:54 
well i add this class to my project, and from the main dialog i create an instance using the Create function and the edit box appears in the app. The problem is that i can't change the content of the edit box. I am a beginner and i think that the m_strMask i somehow not connected to my main dialog. Please help or give me a link to the source or project files...
GeneralWonderful - thank you!memberPaul Roberts26 Jul '08 - 7:50 
What could have taken hours took just a few minutes thanks to your class! Smile | :)
GeneralControl not visiblememberSteve's Mom's Son2 May '05 - 8:47 
I need to instantiate the control on a dialog, but the control is not visible when the dialog displays. I'm using the following code in the OnInitDialog function:
 

RECT DTRect;
DTRect.left = 20;
DTRect.top = 232;
DTRect.right = 165;
DTRect.bottom = 247;
VDateTimeEdit * m_DateTime = new VDateTimeEdit;
m_DateTime->Create(ES_LEFT, DTRect, this, IDC_DATE_TIME);

 
The coordinates of DTRect are within the boundaries of the dialog.
I also commented out the SetDateTime function call in the PreSubclassWindow method as suggested by another poster.
 
1) Is OnInitDialog the right place to create the control?
2) Is "this" the correct value for the 3rd parameter?
 
Can anyone help with this?

GeneralSome slight modificationsmemberKenGuru24 Aug '04 - 3:35 
Hi everybody,
first to say: great code. Easy to modify. Nice.
All i needed for date or time.
I've added some functionality:
1. Added macros for INCREMENTING/DECREMENTING the cursor to the next/previous masked char like numeric input does. Fixed chars will be left out.
2. causing VK_BACK to delete the according char and replace it with a '0' like the normal numeric input will do.
 
Modify your code to the following:
 
#define INCREMENT_CURSOR() if (++pos < m_strMask.GetLength())\
for ( c = m_strMask[pos]; pos < m_strMask.GetLength()\
&& c != 'D' && c != 'M' \
&& c != 'Y' && c != 'h' \
&& c != 'm' && c != 's';\
c = m_strMask[pos])\
pos++;
 
#define DECREMENT_CURSOR() if (--pos > 0)\
for ( c = m_strMask[pos]; pos > 0 \
&& c != 'D' && c != 'M' \
&& c != 'Y' && c != 'h' \
&& c != 'm' && c != 's';\
c = m_strMask[pos]) \
pos--;
 
Modify the code in the OnChar handler
 
from:
 
CString old;
GetWindowText(old);
int pos;
TCHAR c;
GetSel(pos, pos);
switch (nChar) {
case VK_BACK: pos--;
break;
case VK_UP: pos--;
break;
case '0':case '1':case '2':case '3':
case '4':case '5':case '6':case '7':
case '8':case '9':
 

to:
 
CString old;
GetWindowText(old);
int pos;
TCHAR c;
GetSel(pos, pos);
int nAddPos=0;
switch (nChar) {
case VK_UP: pos--;
break;
case VK_BACK:
nAddPos = pos;
DECREMENT_CURSOR();
nAddPos = pos-nAddPos;
nChar = '0';
if( pos <0)
{
pos = 0;
break;
}

case '0':case '1':case '2':case '3':
case '4':case '5':case '6':case '7':
case '8':case '9':
 
Add the nAddPos param to SetSel like:
SetSel(pos+nAddPos, pos+nAddPos);
 
Last but not least add in the OnKeyDown handler:
case VK_RIGHT:
{
int pos;
TCHAR c;
GetSel(pos, pos);
INCREMENT_CURSOR();

SetSel(pos, pos);
}
break;
 
case VK_LEFT:
{
int pos;
TCHAR c;
GetSel(pos, pos);
DECREMENT_CURSOR();
 
SetSel(pos, pos);
}
break;
 

GeneralGood butmemberStudent00728 Mar '04 - 6:02 
Could anybody fix it that date can be changed or edit by input?
GeneralGreat Code!membersoftyan7 Oct '03 - 18:14 
This is what i'm looking for!
 
Thanks again!
GeneralRe: Great Code!membersoftyan7 Oct '03 - 18:17 
Also thanks to Trevor Ash, without his comments i cann't even implement this Class.
GeneralBUGmemberAizz3 Jul '03 - 15:29 
You excellcent work help me a lot! But I've met a little problem:
 
void OnChar(...)
{
...
case '0':case '1':case '2':case '3':
case '4':case '5':case '6':case '7':
case '8':case '9':
...
 
// Next lines may cause the edit control uneditable
// Reason: invalid date return by GetDateTime()
// cause the status of dt is invalid,
// so the SetDateTime() function won't be called.
COleDateTime dt = GetDateTime(str);
if (dt.GetStatus() == COleDateTime::valid)
SetDateTime(dt);
 
...
break;
...
}
 
To make the GetDateTime() function return a invalid date:
1. Type month:12, day 31
2. Change first digit of month to 0
See the return date--"02/31"!
 
Solution:
Restrict the return date of GetDateTime() function, 31 or 30 days in a month, 28 or 29 days in Feb... a little Dead | X| , hoho~~

 
Pardon me for my poor English...
GeneralNULL DATEmemberSteven Henthorn4 Jun '03 - 4:17 
I would first like to let you know that this control rocks!Cool | :cool:
It is very usefull.
 
I would like to know if you could help me.
I would like to have a default character used as a place holder for
all valid digit entry positions ie if the mask character was '_' then the
deafault view ( after reset) would be __/__/____ if the mask was MM/DD/YYYY.
 
This could represent a NULL date.
 
I would also like the backspace to remove any digit and replace with the mask character.
i.e.
before backspace ( cursor in day after second digit ): 12/25/2003
after backspace 12/2_/2003.
 
Thanks for taking the time to help. Smile | :)

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web01 | 2.6.130523.1 | Last Updated 26 Mar 2000
Article Copyright 2000 by Tri VU KHAC
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid