Introduction
Since I plan to retire the 32-bit C library that I created years ago to support the system utilities that I was creating, mostly in C, with a sprinkling of standard C++ in favor of a new oune built around the strftime
function that is part of the standard C runtime library, I wanted a ready reference that showed unequivocally what each token does. I harvested my list of substitution tokens from https://msdn.microsoft.com/en-us/library/fe06s4ak.aspx.
Background
Unless you use a string formatting routine pretty regularly, it's hard to remember what each of the various substitution tokens for such library routines as strftime
and printf
do. Compounding the matter is that some of the descriptions are a tad confusing. My solution is a tool that uses each token, in turn, in a call to strftime
. As a bonus, the report starts with a display of the current machine time that uses the most common tokens. Since I needed to get the data into Excel, so that I could add it to my workbook of quick references, the tool creates a tab delimited report as a by-product.
Following is the output written to the console by the version of the tool that is included in the code sample.
Microsoft Windows [Version 10.0.17134.112]
(c) 2018 Microsoft Corporation. All rights reserved.
C:\Users\DAG\Documents\Articles_2018\NTFS_File_Times_in_CMD\SpiderOak_Directory_Watcher_etc\spideroak_windows_dir_watcher\NOTES 2018/06/20 14:09:25.68>F:\Source_Code\Visual_Studio\Projects\_Laboratory\StrfTime_Token_Demo\Release\StrfTime_Token_Demo.exe
Showing all 23 individual strftime substitution tokens, raw and modifed:
Time used for all tests = 2018/06/20 14:09:33 Central Daylight Time
Case 1: Abbreviated weekday name:
Raw Token: %a = Wed
Modified Token: %#a = Wed
Case 2: Full weekday name:
Raw Token: %A = Wednesday
Modified Token: %#A = Wednesday
Case 3: Abbreviated month name:
Raw Token: %b = Jun
Modified Token: %#b = Jun
Case 4: Full month nam:
Raw Token: %B = June
Modified Token: %#B = June
Case 5: Date and time representation appropriate for locale:
Raw Token: %c = Wed Jun 20 14:09:33 2018
Modified Token: %#c = Wednesday, June 20, 2018 14:09:33
Case 6: Day of month as decimal number (01 ? 31):
Raw Token: %d = 20
Modified Token: %#d = 20
Case 7: Hour in 24-hour format (00 ? 23):
Raw Token: %H = 14
Modified Token: %#H = 14
Case 8: Hour in 12-hour format (01 ? 12):
Raw Token: %I = 02
Modified Token: %#I = 2
Case 9: Day of year as decimal number (001 ? 366):
Raw Token: %j = 171
Modified Token: %#j = 171
Case 10: Month as decimal number (01 ? 12):
Raw Token: %m = 06
Modified Token: %#m = 6
Case 11: Minute as decimal number (00 ? 59):
Raw Token: %M = 09
Modified Token: %#M = 9
Case 12: Current locale's A.M./P.M. indicator for 12-hour clock:
Raw Token: %p = PM
Modified Token: %#p = PM
Case 13: Second as decimal number (00 ? 59):
Raw Token: %S = 33
Modified Token: %#S = 33
Case 14: Week of year as decimal number, with Sunday as first day of week (00 ? 53):
Raw Token: %U = 24
Modified Token: %#U = 24
Case 15: Week of year as decimal number, with Monday as first day of week (00 ? 53):
Raw Token: %W = 25
Modified Token: %#W = 25
Case 16: Weekday as decimal number (0 ? 6; Sunday is 0):
Raw Token: %w = 3
Modified Token: %#w = 3
Case 17: Date representation for current locale:
Raw Token: %x = 06/20/18
Modified Token: %#x = Wednesday, June 20, 2018
Case 18: Time representation for current locale:
Raw Token: %X = 14:09:33
Modified Token: %#X = 14:09:33
Case 19: Year without century, as decimal number (00 ? 99):
Raw Token: %y = 18
Modified Token: %#y = 18
Case 20: Year with century, as decimal number:
Raw Token: %Y = 2018
Modified Token: %#Y = 2018
Case 21: Either the time-zone name or time zone abbreviation, depending on registry settings; no characters if time zone is unknown:
Raw Token: %z = -0500
Modified Token: %#z = -0500
Case 22: Either the time-zone name or time zone abbreviation, depending on registry settings; no characters if time zone is unknown:
Raw Token: %Z = Central Daylight Time
Modified Token: %#Z = Central Daylight Time
Case 23: Percent sign (The doubled percent sign behaves in the standard way to suppress the default intepretation of the % character.):
Raw Token: %% = %
Modified Token: %#% = %
A tab-delmited report is in C:\Users\DAG\Documents\Articles_2018\NTFS_File_Times_in_CMD\SpiderOak_Directory_Watcher_etc\spideroak_windows_dir_watcher\NOTES\StrfTime_Token_Demo.TSV.
Done: press Enter to exit program.
C:\Users\DAG\Documents\Articles_2018\NTFS_File_Times_in_CMD\SpiderOak_Directory_Watcher_etc\spideroak_windows_dir_watcher\NOTES 2018/06/20 14:09:57.86>
Sample archive strftime_Samples_20180620_145841.zip
contains two files.
StrfTime_Token_Demo_Debug_20180619_223755.TSV
is the report generated by a previous run, before I added the name of the output file to the end of the report. StrfTime_Token_Demo_Debug_20180619_223755.xlsx
is the Excel worksheet that I created by importing the tab delimited file listed just above.
Since the files were generated by the last run before I added the file name to the report, the reference time stamp and the 23 output values differ from those shown above.
Using the code
Since such a tool can benefit any programmer who writes in C and C++, I took exceptional pains to confine myself to standard library routines, so that the source should compile on any platform that supports one or more ANSI C compilers.
If your platform is Microsoft Windows and your C compiler is any recent Microsoft Visual C++ edition, the solution should open and build as is. The project includes both debug and release configuration build outputs generated on my machine, which runs Visual Studio 2017 atop Windows 10 Pro x64, version 1803. Although the solution defines both x86 and x64 configurations, I built only the x86 configurations, leaving the x64 build as an exercise.
To build it on another OS or with a different compiler, the only part that you may need to adjust is the first header, targetver.h
, which contains yet another include directive.
#pragma once
#include <SDKDDKVer.h>
Unless your build tools include SDKDDKVer.h
, you must find a suitable substitute. For your reference, below is a listing of its contents.
#ifndef _INC_SDKDDKVER
#define _INC_SDKDDKVER
#if (_MSC_VER >= 800)
#if (_MSC_VER >= 1200)
#pragma warning(push)
#pragma warning(disable:4668) /* #if not_defined treated as #if 0 */
#endif
#pragma warning(disable:4001) /* nonstandard extension : single line comment */
#endif
#pragma once
#define _WIN32_WINNT_NT4 0x0400
#define _WIN32_WINNT_WIN2K 0x0500
#define _WIN32_WINNT_WINXP 0x0501
#define _WIN32_WINNT_WS03 0x0502
#define _WIN32_WINNT_WIN6 0x0600
#define _WIN32_WINNT_VISTA 0x0600
#define _WIN32_WINNT_WS08 0x0600
#define _WIN32_WINNT_LONGHORN 0x0600
#define _WIN32_WINNT_WIN7 0x0601
#define _WIN32_WINNT_WIN8 0x0602
#define _WIN32_WINNT_WINBLUE 0x0603
#define _WIN32_WINNT_WINTHRESHOLD 0x0A00 /* ABRACADABRA_THRESHOLD*/
#define _WIN32_WINNT_WIN10 0x0A00 /* ABRACADABRA_THRESHOLD*/
#define _WIN32_IE_IE20 0x0200
#define _WIN32_IE_IE30 0x0300
#define _WIN32_IE_IE302 0x0302
#define _WIN32_IE_IE40 0x0400
#define _WIN32_IE_IE401 0x0401
#define _WIN32_IE_IE50 0x0500
#define _WIN32_IE_IE501 0x0501
#define _WIN32_IE_IE55 0x0550
#define _WIN32_IE_IE60 0x0600
#define _WIN32_IE_IE60SP1 0x0601
#define _WIN32_IE_IE60SP2 0x0603
#define _WIN32_IE_IE70 0x0700
#define _WIN32_IE_IE80 0x0800
#define _WIN32_IE_IE90 0x0900
#define _WIN32_IE_IE100 0x0A00
#define _WIN32_IE_IE110 0x0A00 /* ABRACADABRA_THRESHOLD */
#define _WIN32_IE_NT4 _WIN32_IE_IE20
#define _WIN32_IE_NT4SP1 _WIN32_IE_IE20
#define _WIN32_IE_NT4SP2 _WIN32_IE_IE20
#define _WIN32_IE_NT4SP3 _WIN32_IE_IE302
#define _WIN32_IE_NT4SP4 _WIN32_IE_IE401
#define _WIN32_IE_NT4SP5 _WIN32_IE_IE401
#define _WIN32_IE_NT4SP6 _WIN32_IE_IE50
#define _WIN32_IE_WIN98 _WIN32_IE_IE401
#define _WIN32_IE_WIN98SE _WIN32_IE_IE50
#define _WIN32_IE_WINME _WIN32_IE_IE55
#define _WIN32_IE_WIN2K _WIN32_IE_IE501
#define _WIN32_IE_WIN2KSP1 _WIN32_IE_IE501
#define _WIN32_IE_WIN2KSP2 _WIN32_IE_IE501
#define _WIN32_IE_WIN2KSP3 _WIN32_IE_IE501
#define _WIN32_IE_WIN2KSP4 _WIN32_IE_IE501
#define _WIN32_IE_XP _WIN32_IE_IE60
#define _WIN32_IE_XPSP1 _WIN32_IE_IE60SP1
#define _WIN32_IE_XPSP2 _WIN32_IE_IE60SP2
#define _WIN32_IE_WS03 0x0602
#define _WIN32_IE_WS03SP1 _WIN32_IE_IE60SP2
#define _WIN32_IE_WIN6 _WIN32_IE_IE70
#define _WIN32_IE_LONGHORN _WIN32_IE_IE70
#define _WIN32_IE_WIN7 _WIN32_IE_IE80
#define _WIN32_IE_WIN8 _WIN32_IE_IE100
#define _WIN32_IE_WINBLUE _WIN32_IE_IE100
#define _WIN32_IE_WINTHRESHOLD _WIN32_IE_IE110 /* ABRACADABRA_THRESHOLD */
#define _WIN32_IE_WIN10 _WIN32_IE_IE110 /* ABRACADABRA_THRESHOLD */
#define NTDDI_WIN2K 0x05000000
#define NTDDI_WIN2KSP1 0x05000100
#define NTDDI_WIN2KSP2 0x05000200
#define NTDDI_WIN2KSP3 0x05000300
#define NTDDI_WIN2KSP4 0x05000400
#define NTDDI_WINXP 0x05010000
#define NTDDI_WINXPSP1 0x05010100
#define NTDDI_WINXPSP2 0x05010200
#define NTDDI_WINXPSP3 0x05010300
#define NTDDI_WINXPSP4 0x05010400
#define NTDDI_WS03 0x05020000
#define NTDDI_WS03SP1 0x05020100
#define NTDDI_WS03SP2 0x05020200
#define NTDDI_WS03SP3 0x05020300
#define NTDDI_WS03SP4 0x05020400
#define NTDDI_WIN6 0x06000000
#define NTDDI_WIN6SP1 0x06000100
#define NTDDI_WIN6SP2 0x06000200
#define NTDDI_WIN6SP3 0x06000300
#define NTDDI_WIN6SP4 0x06000400
#define NTDDI_VISTA NTDDI_WIN6
#define NTDDI_VISTASP1 NTDDI_WIN6SP1
#define NTDDI_VISTASP2 NTDDI_WIN6SP2
#define NTDDI_VISTASP3 NTDDI_WIN6SP3
#define NTDDI_VISTASP4 NTDDI_WIN6SP4
#define NTDDI_LONGHORN NTDDI_VISTA
#define NTDDI_WS08 NTDDI_WIN6SP1
#define NTDDI_WS08SP2 NTDDI_WIN6SP2
#define NTDDI_WS08SP3 NTDDI_WIN6SP3
#define NTDDI_WS08SP4 NTDDI_WIN6SP4
#define NTDDI_WIN7 0x06010000
#define NTDDI_WIN8 0x06020000
#define NTDDI_WINBLUE 0x06030000
#define NTDDI_WINTHRESHOLD 0x0A000000 /* ABRACADABRA_THRESHOLD */
#define NTDDI_WIN10 0x0A000000 /* ABRACADABRA_THRESHOLD */
#define NTDDI_WIN10_TH2 0x0A000001 /* ABRACADABRA_WIN10_TH2 */
#define NTDDI_WIN10_RS1 0x0A000002 /* ABRACADABRA_WIN10_RS1 */
#define NTDDI_WIN10_RS2 0x0A000003 /* ABRACADABRA_WIN10_RS2 */
#define NTDDI_WIN10_RS3 0x0A000004 /* ABRACADABRA_WIN10_RS3 */
#define NTDDI_WIN10_RS4 0x0A000005 /* ABRACADABRA_WIN10_RS4 */
#define WDK_NTDDI_VERSION NTDDI_WIN10_RS4 /* ABRACADABRA_WIN10_RS4 */
#define OSVERSION_MASK 0xFFFF0000
#define SPVERSION_MASK 0x0000FF00
#define SUBVERSION_MASK 0x000000FF
#define OSVER(Version) ((Version) & OSVERSION_MASK)
#define SPVER(Version) (((Version) & SPVERSION_MASK) >> 8)
#define SUBVER(Version) (((Version) & SUBVERSION_MASK) )
#if defined(DECLSPEC_DEPRECATED_DDK)
#if (NTDDI_VERSION >= NTDDI_WIN2K)
#define DECLSPEC_DEPRECATED_DDK_WIN2K DECLSPEC_DEPRECATED_DDK
#else
#define DECLSPEC_DEPRECATED_DDK_WIN2K
#endif
#if (NTDDI_VERSION >= NTDDI_WINXP)
#define DECLSPEC_DEPRECATED_DDK_WINXP DECLSPEC_DEPRECATED_DDK
#else
#define DECLSPEC_DEPRECATED_DDK_WINXP
#endif
#if (NTDDI_VERSION >= NTDDI_WS03)
#define DECLSPEC_DEPRECATED_DDK_WIN2003 DECLSPEC_DEPRECATED_DDK
#else
#define DECLSPEC_DEPRECATED_DDK_WIN2003
#endif
#if (NTDDI_VERSION >= NTDDI_WIN6)
#define DECLSPEC_DEPRECATED_DDK_WIN6 DECLSPEC_DEPRECATED_DDK
#else
#define DECLSPEC_DEPRECATED_DDK_WIN6
#endif
#define DECLSPEC_DEPRECATED_DDK_LONGHORN DECLSPEC_DEPRECATED_DDK_WIN6
#endif // defined(DECLSPEC_DEPRECATED_DDK)
#define NTDDI_VERSION_FROM_WIN32_WINNT2(ver) ver##0000
#define NTDDI_VERSION_FROM_WIN32_WINNT(ver) NTDDI_VERSION_FROM_WIN32_WINNT2(ver)
#if !defined(_WIN32_WINNT) && !defined(_CHICAGO_)
#define _WIN32_WINNT 0x0A00
#endif
#ifndef NTDDI_VERSION
#ifdef _WIN32_WINNT
#if (_WIN32_WINNT <= _WIN32_WINNT_WINBLUE)
#define NTDDI_VERSION NTDDI_VERSION_FROM_WIN32_WINNT(_WIN32_WINNT)
#elif (_WIN32_WINNT >= _WIN32_WINNT_WIN10)
#define NTDDI_VERSION WDK_NTDDI_VERSION
#endif // (_WIN32_WINNT <= _WIN32_WINNT_WINBLUE)
#else
#define NTDDI_VERSION 0x0A000005
#endif // _WIN32_WINNT
#endif // NTDDI_VERSION
#ifndef WINVER
#ifdef _WIN32_WINNT
#define WINVER _WIN32_WINNT
#else
#define WINVER 0x0A00
#endif
#endif
#ifndef _WIN32_IE
#ifdef _WIN32_WINNT
#if (_WIN32_WINNT <= _WIN32_WINNT_NT4)
#define _WIN32_IE _WIN32_IE_IE50
#elif (_WIN32_WINNT <= _WIN32_WINNT_WIN2K)
#define _WIN32_IE _WIN32_IE_IE501
#elif (_WIN32_WINNT <= _WIN32_WINNT_WINXP)
#define _WIN32_IE _WIN32_IE_IE60
#elif (_WIN32_WINNT <= _WIN32_WINNT_WS03)
#define _WIN32_IE _WIN32_IE_WS03
#elif (_WIN32_WINNT <= _WIN32_WINNT_VISTA)
#define _WIN32_IE _WIN32_IE_LONGHORN
#elif (_WIN32_WINNT <= _WIN32_WINNT_WIN7)
#define _WIN32_IE _WIN32_IE_WIN7
#elif (_WIN32_WINNT <= _WIN32_WINNT_WIN8)
#define _WIN32_IE _WIN32_IE_WIN8
#else
#define _WIN32_IE 0x0A00
#endif
#else
#define _WIN32_IE 0x0A00
#endif
#endif
#if defined(_WIN32_WINNT) && !defined(MIDL_PASS) && !defined(RC_INVOKED)
#if (defined(WINVER) && (WINVER < 0x0400) && (_WIN32_WINNT > 0x0400))
#error WINVER setting conflicts with _WIN32_WINNT setting
#endif
#if (((OSVERSION_MASK & NTDDI_VERSION) == NTDDI_WIN2K) && (_WIN32_WINNT != _WIN32_WINNT_WIN2K))
#error NTDDI_VERSION setting conflicts with _WIN32_WINNT setting
#endif
#if (((OSVERSION_MASK & NTDDI_VERSION) == NTDDI_WINXP) && (_WIN32_WINNT != _WIN32_WINNT_WINXP))
#error NTDDI_VERSION setting conflicts with _WIN32_WINNT setting
#endif
#if (((OSVERSION_MASK & NTDDI_VERSION) == NTDDI_WS03) && (_WIN32_WINNT != _WIN32_WINNT_WS03))
#error NTDDI_VERSION setting conflicts with _WIN32_WINNT setting
#endif
#if (((OSVERSION_MASK & NTDDI_VERSION) == NTDDI_VISTA) && (_WIN32_WINNT != _WIN32_WINNT_VISTA))
#error NTDDI_VERSION setting conflicts with _WIN32_WINNT setting
#endif
#if ((_WIN32_WINNT < _WIN32_WINNT_WIN2K) && (_WIN32_IE > _WIN32_IE_IE60SP1))
#error _WIN32_WINNT settings conflicts with _WIN32_IE setting
#endif
#endif // defined(_WIN32_WINNT) && !defined(MIDL_PASS) && !defined(_WINRESRC_)
#if (_MSC_VER >= 800)
#if (_MSC_VER >= 1200)
#pragma warning(pop)
#else
#pragma warning(default:4001) /* nonstandard extension : single line comment */
#endif
#endif
#endif /* !_INC_SDKDDKVER */
To emphasize that the implementation is ANSI C, the one and only source file is StrfTime_Token_Demo.C
.
Points of Interest
Though the program is as straightforward as its task, there are a few notwworthy items, starting with the preprocessor symbol definitions that immediately follow the CRT library headers.
#define ARRAY_FIRAT_ELEMENT_SUBSCRIPT 0
#define ARRAY_INVALID_SUBSCRIPT -1
#define ARRAY_ORDINAL_FROM_SUBSCRIPT 1
#define CONSOLE_INPUT_BUFFER_SIZE 100
#define CRT_TIME_ERROR -1
#define CRT_TIME_CONVERSION_ERROR 0
#define OUTPUT_FILENAME L"StrfTime_Token_Demo.TSV"
#define STRFTIME_DEFAULT_FORMAT L"%Y/%m/%d %H:%M:%S %Z"
#define STRFTIME_OUTBUF_SIZE 64
#define STRFTIME_OUTPUT_MAX_CHARS_OUT ( STRFTIME_OUTBUF_SIZE - 1 )
#define RETCODE_SUCCESS 0
#define RETCODE_STRFTIME_ERROR 255
If a literal appears more than once or its meaning depends on its context, it gets a preprocessor macro.
const wchar_t * m_alpszRawFormatStrings [ ] = {
L"%a" ,
L"%A" ,
L"%b" ,
L"%B" ,
L"%c" ,
L"%d" ,
L"%H" ,
L"%I" ,
L"%j" ,
L"%m" ,
L"%M" ,
L"%p" ,
L"%S" ,
L"%U" ,
L"%W" ,
L"%w" ,
L"%x" ,
L"%X" ,
L"%y" ,
L"%Y" ,
L"%z" ,
L"%Z" ,
L"%%" };
const wchar_t * m_alpszModifiedFormatStrings [ ] = {
L"%#a" ,
L"%#A" ,
L"%#b" ,
L"%#B" ,
L"%#c" ,
L"%#d" ,
L"%#H" ,
L"%#I" ,
L"%#j" ,
L"%#m" ,
L"%#M" ,
L"%#p" ,
L"%#S" ,
L"%#U" ,
L"%#W" ,
L"%#w" ,
L"%#x" ,
L"%#X" ,
L"%#y" ,
L"%#Y" ,
L"%#z" ,
L"%#Z" ,
L"%#%" };
const wchar_t * m_alpszFormatDescriptions [ ] = {
L"Abbreviated weekday name" ,
L"Full weekday name" ,
L"Abbreviated month name" ,
L"Full month nam" ,
L"Date and time representation appropriate for locale" ,
L"Day of month as decimal number (01 – 31)" ,
L"Hour in 24-hour format (00 – 23)" ,
L"Hour in 12-hour format (01 – 12)" ,
L"Day of year as decimal number (001 – 366)" ,
L"Month as decimal number (01 – 12)" ,
L"Minute as decimal number (00 – 59)" ,
L"Current locale's A.M./P.M. indicator for 12-hour clock" ,
L"Second as decimal number (00 – 59)" ,
L"Week of year as decimal number, with Sunday as first day of week (00 – 53)" ,
L"Week of year as decimal number, with Monday as first day of week (00 – 53)" ,
L"Weekday as decimal number (0 – 6; Sunday is 0)" ,
L"Date representation for current locale" ,
L"Time representation for current locale" ,
L"Year without century, as decimal number (00 – 99)" ,
L"Year with century, as decimal number" ,
L"Either the time-zone name or time zone abbreviation, depending on registry settings; no characters if time zone is unknown" ,
L"Either the time-zone name or time zone abbreviation, depending on registry settings; no characters if time zone is unknown" ,
L"Percent sign (The doubled percent sign behaves in the standard way to suppress the default intepretation of the % character.)" };
The foregoing three arrays could easily have been organized into a single multi-dimensional array, but it was simpler to keep them separate, since all three are processed by the same loop, which uses the same index as the subscript for all three.
Finally, all buffers are defined at module scope, so that they get baked into the code, which gets them initialized by the compiler, and has no effect on the memory footprint of the program, since all are used for its entire lifetime.
History
Wednesday, 20 June 2018, this article was submitted for publication.