Click here to Skip to main content
11,489,549 members (76,629 online)
Click here to Skip to main content

Date-Time Edit Control

, 25 Mar 2000 131.8K 45
Rate this:
Please Sign up or sign in to vote.
A simple masked date-time editor.

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

Share

About the Author

Tri VU KHAC

Belgium Belgium
No Biography provided

Comments and Discussions

 
Suggestionmy attempt using Allow Edit property Pin
zriniadhi3-Oct-11 8:44
memberzriniadhi3-Oct-11 8:44 
Generalproblem with functionality Pin
Kipo Kiposki5-Apr-10 13:54
memberKipo Kiposki5-Apr-10 13:54 
GeneralWonderful - thank you! Pin
Paul Roberts26-Jul-08 8:50
memberPaul Roberts26-Jul-08 8:50 
GeneralControl not visible Pin
Steve's Mom's Son2-May-05 9:47
memberSteve's Mom's Son2-May-05 9:47 
GeneralSome slight modifications Pin
KenGuru24-Aug-04 4:35
memberKenGuru24-Aug-04 4:35 
GeneralGood but Pin
Student00728-Mar-04 7:02
memberStudent00728-Mar-04 7:02 
GeneralGreat Code! Pin
softyan7-Oct-03 19:14
membersoftyan7-Oct-03 19:14 
GeneralRe: Great Code! Pin
softyan7-Oct-03 19:17
membersoftyan7-Oct-03 19:17 
GeneralBUG Pin
Aizz3-Jul-03 16:29
memberAizz3-Jul-03 16:29 
GeneralNULL DATE Pin
Steven Henthorn4-Jun-03 5:17
memberSteven Henthorn4-Jun-03 5:17 
GeneralTo the Author of this project Pin
cweng20033-May-03 19:28
membercweng20033-May-03 19:28 
GeneralThank you very much Pin
Albert Hermann13-Feb-03 2:33
memberAlbert Hermann13-Feb-03 2:33 
GeneralProblem when something selected Pin
mordred8-Oct-02 0:34
sussmordred8-Oct-02 0:34 
GeneralProblem when something selected Pin
mordred8-Oct-02 0:01
sussmordred8-Oct-02 0:01 
GeneralInitialize in GetDateTime Pin
cf12-Jul-02 9:26
susscf12-Jul-02 9:26 
GeneralNot able to create Pin
Anonymous12-Apr-02 0:34
memberAnonymous12-Apr-02 0:34 
GeneralRe: Not able to create Pin
TMS_7311-Nov-03 4:00
memberTMS_7311-Nov-03 4:00 
QuestionWhat about adding Spin Button? Pin
Anonymous11-Oct-01 1:40
memberAnonymous11-Oct-01 1:40 
AnswerRe: What about adding Spin Button? Pin
Olivier Zegdoun15-Aug-02 13:03
memberOlivier Zegdoun15-Aug-02 13:03 
GeneralRe: What about adding Spin Button? Pin
Tommy Keegan6-Aug-06 18:18
memberTommy Keegan6-Aug-06 18:18 
GeneralClean, simple and versatile Pin
Rod Hamilton11-Jul-01 15:04
memberRod Hamilton11-Jul-01 15:04 
GeneralPerfect for my needs Pin
Trevor Ash7-Apr-01 8:19
memberTrevor Ash7-Apr-01 8:19 
Others may have complained a bit, but I have found this code to perfectly suit my needs. I think you're correct in saying that one reason other solutions don't work as well is because they try to encompass too many masks into one control. I've been searching for this tool and I've already seen about 5 to 10 others that just don't work well.

This really was cut-n-paste for those that are asking for the source code. Just create a new class using class wizard called VDateTimeEdit and derive it from CEdit. Add handlers through CW for all of the window messages that are shown in the sample code. Then add the extra methods and properties required for the rest of the class. It takes a whole 3 minutes of time.

Personally, I only wanted the Date portion and not the time. All I had to do was modify the mask string by removing the "hh mm ss" from it in the constructor. The only other thing that felt funny to me was the initial "DD/MM/YYYY" format. Once again, it took only 2 seconds to modify the mask in the constructor from "DD/MM/YYYY" to "MM/DD/YYYY".

This works perfect from me because I use COleDateTime everywhere else in my code. Also, for those of you that didn't notice from the code, up and down arrows are handled to automatically increment/decrement the date/time that the cursor is at, and it actually works as expected. You can still select all of the text with a mouse AND with the keyboard (many others don't allow that).

Thank you very much. Good, Simple, Efficient!Roll eyes | :rolleyes:
GeneralA version of the date/time entry field that doesn't use OLE Pin
Peter Kovach9-Jun-00 6:50
sussPeter Kovach9-Jun-00 6:50 
GeneralSource Code Pin
Some programmer28-Mar-00 16:02
sussSome programmer28-Mar-00 16:02 

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

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

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