• The Old New Thing

    Why Daylight Savings Time is nonintuitive

    • 32 Comments
    Welcome Knowledge Base article 932955 readers! Remember, the information on this Web site is not official Microsoft documentation.

    Daylight Savings Time ends this weekend in most of North America and Europe, so it seems a good time to discuss the whole problem of Daylight Savings Time and timestamps.

    A common complaint is that all the time zone conversion functions like FileTimeToLocalFileTime apply the current Daylight Savings Time (DST) bias rather than the bias that was in effect at the time in question.

    For example, suppose you have a FILETIME structure that represents "1 January 2000 12:00AM". If you are in Redmond during the summertime, this converts to "31 December 1999 5:00PM", seven hours difference, even though the time difference between Redmond and UTC was eight hours at that time. (I.e., when people in London were celebrating the new year, it was 4pm in Redmond, not 5pm.)

    The reason is that the time got converted from "1 January 2000 12:00AM UTC" to "31 December 1999 5:00PM PDT". So, technically, the conversion is correct. Of course, nobody was using PDT on December 31, 1999 in Redmond; everybody was on PST.

    Why don't the time zone conversion functions use the time zone appropriate for the time of year?

    One reason is that it means that FileTimeToLocalFileTime and LocalFileTimeToFileTime would no longer be inverses of each other. If you had a local time during the "limbo hour" during the cutover from standard time to daylight time, it would have no corresponding UTC time because there was no such thing as 2:30am local time. (The clock jumped from 2am to 3am.) Similarly, a local time of 2:30am during the cutover from daylight time back to standard time would have two corresponding UTC times.

    Another reason is that the laws regarding daylight savings time are in constant flux. For example, if the year in the example above was 1977 instead of 2000, the conversion would have been correct because the United States was running on year-round Daylight Savings Time due to the energy crisis. Of course, this information isn't encoded anywhere in the TIME_ZONE_INFORMATION structure. Similarly, during World War 2, the United States went on DST all year round. And between 1945 and 1966, the DST rules varied from region to region.

    DST rules are in flux even today. The DST cutover dates in Israel are decided on a year-by-year basis by the Knesset. As a result, there is no deterministic formula for the day, and therefore no way to know it ahead of time.

    (Warning: .NET content ahead; two days in a row, what's gotten into me!?)

    Compare the output of FileInfo.LastWriteTime.ToString("f") with what you see in the property sheet for a file that was last written to on the other side of the DST transition. For example, suppose the file was last modified on October 17, during DST but DST is not currently in effect. Explorer's file properties reports Thursday, October 17, 2003, 8:45:38 AM, but .NETs FileInfo reports Thursday, October 17, 2003, 9:45 AM.

    En gang til for prins Knud: Win32 does not attempt to guess which time zone rules were in effect at that other time. So Win32 says, "Thursday, October 17, 2002 8:45:38 AM PST". Note: Pacific Standard Time. Even though October 17 was during Pacific Daylight Time, Win32 displays the time as standard time because that's what time it is now.

    .NET says, "Well, if the rules in effect now were also in effect on October 17, 2003, then that would be daylight time" so it displays "Thursday, October 17, 2003, 9:45 AM PDT" - daylight time.

    So .NET gives a value which is more intuitively correct, but is also potentially incorrect, and which is not invertible. Win32 gives a value which is intuitively incorrect, but is strictly correct.

    I suspect that the .NET behavior was for compatibility with Visual Basic, but I don't know for sure.

  • The Old New Thing

    Writing a sort comparison function

    • 8 Comments

    When you are writing a sort comparison function (say, to be passed to ListView_SortItems or *gasp* to be used as an IComparer), your comparison function needs to follow these rules:

    • Reflexivity: Compare(a, a) = 0.
    • Anti-Symmetry: Compare(a, b) has the opposite sign of Compare(b, a), where 0 is considered to be its own opposite.
    • Transitivity: If Compare(a, b) ≤ 0 and Compare(b, c) ≤ 0, then Compare(a, c) ≤ 0.

    Here are some logical consequences of these rules (all easily proved). The first two are obvious, but the third may be a surprise.

    • Transitivity of equality: If Compare(a, b) = 0 and Compare(b, c) = 0, then Compare(a, c) = 0.
    • Transitivity of inequality: If Compare(a, b) < 0 and Compare(b, c) < 0, then Compare(a, c) < 0.
    • Substitution: If Compare(a, b) = 0, then Compare(a, c) has the same sign as Compare(b, c).

    Of the original three rules, the first two are hard to get wrong, but the third rule is often hard to get right if you try to be clever in your comparison function.

    For one thing, these rules require that you implement a total order. If you merely have a partial order, you must extend your partial order to a total order in a consistent manner.

    I saw somebody get into trouble when they tried to implement their comparison function on a set of tasks, where some tasks have other tasks as prerequisites. The comparison function implemented the following algorithm:

    • If a is a prerequisite of b (possibly through a chain of intermediate tasks), then a < b.
    • If b is a prerequisite of a (again, possibly through a chain of intermediate tasks), then a > b.
    • Otherwise, a = b. "Neither task is a prerequisite of the other, so I don't care what order they are in."

    Sounds great. Then you can sort with this comparison function and you get the tasks listed in some order such that all tasks come after their prerequisites.

    Except that it doesn't work. Trying to sort with this comparison function results in all the tasks being jumbled together with apparently no regard for which tasks are prerequisites of which. What went wrong?

    Consider this dependency diagram:

       a ----> b
     
       c
    

    Task "a" is a prerequisite for "b", and task "c" is unrelated to both of them. If you used the above comparison function, it would declare that "a = c" and "b = c" (since "c" is unrelated to "a" or "b"), which in turn implies by transitivity that "a = b", which contradicts "a < b", since "a" is a prerequisite for "b". If your comparison function is inconsistent, you will get garbled results.

    Moral of the story: When you write a comparison function, you really have to know which items are less than which other items. Don't just declare two items "equal" because you don't know which order they should be in.

  • The Old New Thing

    What's the deal with those reserved filenames like NUL and CON?

    • 22 Comments

    Set the wayback machine to DOS 1.0.

    DOS 1.0 didn't support subdirectories, lowercase, or filenames longer than 8.3.

    When you ran the assembler (or compiler if you were really fancy) the conversation went something like this:

    A>asm foo    the ".asm" extension on "foo" is implied
    Assembler version blah blah blah
    Source file: FOO.ASM
    Listing file [FOO.LST]:
        just hit Enter to accept the default
    Object file [FOO.OBJ]:     just hit Enter to accept the default
    Assembler cranks away

    You only had to type the base name of the file; the ".LST" and ".OBJ" extensions were appended automatically. In fact, I don't think you could disable the extensions; they were always added.

    But what if you didn't want a listing file? The assembler demanded a filename, and if you didn't type any filename at all, it created one with the same basename as your source file.

    That's where the magic filenames come in. Suppose you wanted the listing file to go straight to the printer. You didn't want to create a file on your floppy drive because there might not be enough space to hold it, or just because you didn't want to waste the time creating a file just to delete it anyway. So you typed "PRN" as the filename.

    Now, the assembler doesn't know about these magic filenames. So the assembler will try to create the file "PRN.LST" and then start writing to it. Little does the assembler realize that the output is actually going to the printer.

    If you wanted to discard the output entirely, you would type "NUL", of course. And if you wanted it to go to the screen, you would type "CON".

    Now, if you followed closely, you can see that the above story explains two things already:

    • Why are the magic filenames magical even if I add an extension?

      Answer: If an extension removed the magic, then when the assembler added ".LST" to the filename, it would no longer be recognized as magical, thereby defeating the purpose of the magic.

    • Why do these magic files exist in every directory?

      Answer: Because DOS 1.0 didn't have subdirectories. There was only one directory, which today we would call the root directory, but back then, since there was no such thing as a subdirectory, there was no need to talk about directories in the first place, much less give the only one you have a name. It was just called "the files on your disk". If magic files didn't work in subdirectories, then when you tried to, for example, chdir into a subdirectory and then run the assembler, you wouldn't be able to type "NUL" as the filename and get the magic.

    But why do we carry these magic filenames forward even today?

    Because everybody still relies on them. Just look at all the batch files that do things like redirect to >NUL or test if a directory exists by asking "if exist directoryname\nul", or all the documentation that says to create a file with "copy CON ...".

  • The Old New Thing

    Using the TAB key to navigate in non-dialogs

    • 4 Comments

    The IsDialogMessage function works even if you aren't a dialog. As long as your child windows have the WS_TABSTOP and/or WS_GROUP styles, they can be navigated as if they were part of a dialog box. One caveat is that IsDialogMessage will send DM_GETDEFID and DM_SETDEFID messages to your window, which are message numbers WM_USER and WM_USER+1, so you should avoid using those messages in your window procedure for some other purpose.

    These changes to our scratch program illustrate how you can use the TAB key to navigate within a non-dialog.

    HWND g_hwndLastFocus;
    
    void OnSetFocus(HWND hwnd, HWND hwndOldFocus)
    {
        if (g_hwndLastFocus) {
            SetFocus(g_hwndLastFocus);
        }
    }
    
    void OnActivate(HWND hwnd, UINT state,
                    HWND hwndActDeact, BOOL fMinimized)
    {
        if (state == WA_INACTIVE) {
            g_hwndLastFocus = GetFocus();
        }
    }
    
    // Just display a messagebox so you can see something
    void OnCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify)
    {
        switch (id) {
        case 100:
            MessageBox(hwnd, TEXT("Button 1 pushed"),
                       TEXT("Title"), MB_OK);
            break;
        case 101:
            MessageBox(hwnd, TEXT("Button 2 pushed"),
                       TEXT("Title"), MB_OK);
            break;
        case IDCANCEL:
            MessageBox(hwnd, TEXT("Cancel pushed"),
                       TEXT("Title"), MB_OK);
            break;
        }
    }
    
    BOOL
    OnCreate(HWND hwnd, LPCREATESTRUCT lpcs)
    {
        HWND hwndChild =
            CreateWindow(
            TEXT("button"),                 /* Class Name */
            TEXT("Button &1"),              /* Title */
            WS_CHILD | WS_VISIBLE | WS_TABSTOP |
            BS_DEFPUSHBUTTON | BS_TEXT,     /* Style */
            0, 0, 100, 100,                 /* Position and size */
            hwnd,                           /* Parent */
            (HMENU)100,                     /* Child ID */
            g_hinst,                        /* Instance */
            0);                             /* No special parameters */
        if (!hwndChild) return FALSE;
        g_hwndLastFocus = hwndChild;
    
        hwndChild =
            CreateWindow(
            TEXT("button"),                 /* Class Name */
            TEXT("Button &2"),              /* Title */
            WS_CHILD | WS_VISIBLE | WS_TABSTOP |
            BS_PUSHBUTTON | BS_TEXT,        /* Style */
            100, 0, 100, 100,               /* Position and size */
            hwnd,                           /* Parent */
            (HMENU)101,                     /* Child ID */
            g_hinst,                        /* Instance */
            0);                             /* No special parameters */
        if (!hwndChild) return FALSE;
    
        hwndChild =
            CreateWindow(
            TEXT("button"),                 /* Class Name */
            TEXT("Cancel"),                 /* Title */
            WS_CHILD | WS_VISIBLE | WS_TABSTOP |
            BS_PUSHBUTTON | BS_TEXT,        /* Style */
            200, 0, 100, 100,               /* Position and size */
            hwnd,                           /* Parent */
            (HMENU)IDCANCEL,                /* Child ID */
            g_hinst,                        /* Instance */
            0);                             /* No special parameters */
        if (!hwndChild) return FALSE;
    
        return TRUE;
    }
    
    //  Add to WndProc
    
        HANDLE_MSG(hwnd, WM_COMMAND, OnCommand);
        HANDLE_MSG(hwnd, WM_ACTIVATE, OnActivate);
        HANDLE_MSG(hwnd, WM_SETFOCUS, OnSetFocus);
    
        // Add blank case statements for these to ensure we don't use them
        // by mistake.
        case DM_GETDEFID: break;
        case DM_SETDEFID: break;
    
    //  Change message loop
        MSG msg;
        while (GetMessage(&msg, NULL, 0, 0)) {
            if (IsDialogMessage(hwnd, &msg)) {
                /* Already handled by dialog manager */
            } else {
                TranslateMessage(&msg);
                DispatchMessage(&msg);
            }
        }
    

    One subtlety is the additional handling of the WM_ACTIVATE and WM_SETFOCUS messages to preserve the focus when the user switches away from the window and back. Notice also that we picked Button 1 as our initial default button by setting it with the BS_DEFPUSHBUTTON style.

    Observe that all the standard dialog accelerators now work. The TAB key navigates, the Alt+1 and Alt+2 keys act as accelerators for the two buttons, the Enter key presses the default button, and the ESC key pushes the Cancel button since its control ID is IDCANCEL.

  • The Old New Thing

    What is the Alt+Tab order?

    • 23 Comments

    What determines the order in which icons appear in the Alt+Tab list?

    The icons appear in the same order as the window Z-order. When you switch to a window, then it comes to the top of the Z-order. If you minimize a window, it goes to the bottom of the Z-order. The Alt+Esc hotkey (gosh, does anybody still use Alt+Esc?) takes the current top window and sends it to the bottom of the Z-order (and the window next in line comes to the top). The Alt+Shift+Esc hotkey (I bet you didn't know that hotkey even existed) takes the bottom-most window and brings it to the top, but does not open the window if it is minimized.

    The presence of "always on top" windows makes this a little more complicated. The basic rule is that an "always on top" window always appears on top of a "not always on top" window. So if the above rules indicate that a "not always on top" window comes to the top, it really just goes as high as it can without getting on top of any "always on top" windows.

    You may have run across the term "fast task switching". This was the term used to describe the precursor to the current Alt+Tab switching interface. The old way of switching via Alt+Tab (Windows 3.0 and earlier) was just like Alt+Esc, except that the window you switched to was automatically opened if it had been minimized. When the new Alt+Tab was added to Windows 3.1, we were concerned that people might prefer the old way, so there was a switch in the control panel to set it back to the slow way. (There is also a setting SPI_SETFASTTASKSWITCH that lets you change it programmatically.) It turns out nobody complained, so the old slow way of task switching was removed entirely and the setting now has no effect.

    This does highlight the effort we take to try to allow people who don't like the new way of doing something to go back to the old way. It turns out that corporations with 10,000 employees don't like it when the user interface changes, because it forces them to spend millions of dollars retraining all their employees. If you open up the Group Policy Editor, you can see the zillions of deployment settings that IT administrators can use to disable a variety of new Windows UI features.

  • The Old New Thing

    The more I learn about Sweden, the weirder it gets

    • 10 Comments

    It must be really boring in Sweden in the winter. Things to do:

    Military service in Sweden is compulsory for all males. When your country has not participated in any wars for two hundred years, you have to pass the time somehow. Apparently one of the exercises is to assess how Sweden would fare against its nearest neighbors. The general consensus (at least in Sweden) is that they could take on Finland and Norway, but if the Danes were to invade, Sweden would eventually succumb, but not before doing the Danish army serious damage.

    Pass the herring.
  • The Old New Thing

    Curling anyone?

    • 9 Comments

    The Granite Curling Club is having their annual open house today, October 18th 2003, from 2pm to 8pm. The Seattle facility is the only dedicated curling facility in the United States west of the Rockies. Bring sweatpants, flat-soled shoes, and $5. They'll provide the brooms and duct tape.

    I became fascinated with curling many years ago when I would run across curling tournaments on Canadian television. The only chance to see curling on television in the States is maybe if you're lucky a match or two during the Winter Olympics.

  • The Old New Thing

    Don't let Marketing mess with your slides

    • 5 Comments

    It's PDC season, so I thought I'd relate an anecdote about a conference from many years ago.

    I forget which conference it was, maybe GCDC 1996, we were all busy preparing our presentations and submitted them to the Microsoft conference representatives so they could apply the standard template, clean them up, print out copies to go into the handouts, all that stuff.

    What about that "clean them up" step?

    We didn't realize what "clean them up" meant until we showed up at the conference and looked at the handouts.

    Part of "cleaning up" was inserting "®" and "™" symbols as necessary. Which meant that they also took every occurrence of the abbreviation "VB" and changed it to "Microsoft Visual Basic®". They even did this to the presentation on vertex buffers. The abbreviation for vertex buffers is also "VB".

    You can imagine what this did to the presentation on vertex buffers.
  • The Old New Thing

    Scrollbars redux: Part 12

    • 0 Comments

    Reader Jeff Miller added Word-style scroll tips to our sample scrollbar program. Here's how he did it:

    HWND g_hwndTT;              /* tooltip control */
    TOOLINFO g_ti;              /* tooltip info struct */
    BOOL g_fInScroll;           /* Scroll state */
    
    void CancelScrollTip()
    {
        if (g_fInScroll) {
            g_fInScroll = FALSE;
            SendMessage(g_hwndTT, TTM_TRACKACTIVATE,
                        FALSE, (LPARAM)&g_ti);
        }
    }
    
    void UpdateScrollTip()
    {
        if (!g_hwndTT) return;
        if (!g_fInScroll) {
            g_fInScroll = TRUE;
            DWORD dwPos = GetMessagePos();
            SendMessage(g_hwndTT, TTM_TRACKPOSITION, 0,
                        MAKELPARAM(GET_X_LPARAM(dwPos) +
                                   GetSystemMetrics(SM_CXVSCROLL)
                                   GET_Y_LPARAM(dwPos)));
            SendMessage(g_hwndTT, TTM_TRACKACTIVATE, TRUE,
                        (LPARAM)&g_ti);
        }
        // Reset the text to LPSTR_TEXTCALLBACK so it will get
        // re-fetched
        SendMessage(g_hwndTT, TTM_SETTOOLINFO, 0,
                    (LPARAM)&g_ti);
    }
    
    LRESULT
    OnNotify(HWND hwnd, int id, LPNMHDR pnm)
    {
        switch (pnm->code) {
        case TTN_GETDISPINFO:
            {
                NMTTDISPINFO *pnmtdi = (NMTTDISPINFO*)pnm;
                wnsprintf(pnmtdi->lpszText, 80,
                          TEXT("Line: %d"), g_yOrigin);
            }
            break;
        }
        return 0;
    }
    
    // change case statement in OnVscroll:
        case SB_THUMBTRACK:     ScrollTo(hwnd, GetTrackPos(hwnd, SB_VERT));
                                UpdateScrollTip(); break;
    // add case statement to OnVscroll:
        case SB_ENDSCROLL:      CancelScrollTip(); break;
    
    // add to OnCreate
    
        /* Create and initialize the tooltip */
        g_hwndTT = CreateWindow(TOOLTIPS_CLASS, NULL,
                 WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP,
                 CW_USEDEFAULT, CW_USEDEFAULT,
                 CW_USEDEFAULT, CW_USEDEFAULT,
                 hwnd, NULL, NULL, NULL);
        if (g_hwndTT) {
            g_ti.cbSize = sizeof(g_ti);
            g_ti.uFlags = TTF_TRACK;
            g_ti.hwnd = hwnd;
            g_ti.lpszText = LPSTR_TEXTCALLBACK;
            SendMessage(g_hwndTT, TTM_ADDTOOL, 0, (LPARAM)&g_ti);
        }
    
    // add to WndProc
        case WM_NOTIFY: return OnNotify(hwnd, (int)wParam,
                                        (LPNMHDR)lParam);
    

    A tracking tooltip is used to display the scroll tip because we don't want the tooltip to do automatic positioning or automatic show/hide.

    When a tracking scroll occurs (SB_THUMBTRACK), we update the scroll tip, displaying it if necessary. When the tracking scroll ends (SB_ENDSCROLL), we remove the tip.

    The above code mimics the Word XP style, where the tooltip appears near the original thumb position but does not follow the thumb as the user scrolls. If you prefer that the tooltip follow the thumb, remove the if (!g_fInScroll) test from the UpdateScrollTip function.

  • The Old New Thing

    The much-misunderstood "nop" action

    • 6 Comments
    Last month, the printers were reconfigured in our building and we got an announcement that went like this:

    Subject: Printer/Copier Reconfiguration

    Microsoft Digital Campus Program

    *Action Required*

    blah blah blah printers are being reconfigured blah blah blah

    Action(s) to be taken: No action is required as the print path information will remain the same.
    Sometimes you have to do nothing, and that counts as doing something.
Page 419 of 429 (4,287 items) «417418419420421»