Changer la date de création/modification de fichiers avec un équivalent du "touch" unix/ms-dos

Description

Salut à tous !

Je poste ici le code source d'un petit utilitaire qui me permet de changer la date de création, de dernière modification ou de dernier accès d'un fichier, sous Windows.
Code écrit en C++/MFC, sous Visual C++ 6.0, pour toutes versions de Windows.
J'ai passé plus de temps à essayer de trouver un programme déjà tout fait ... que de l'écrire !
Coté source, j'ai vu celui de Nebula sur ce site, mais je cherchais un utilitaire un peu plus complet, qui soit à la fois simple et puissant (récursif), et puisse être appelé par un fichier "batch" (.bat), exactement comme le bon vieux touch.exe de MS-DOS... qui est devenu introuvable depuis !
Par rapport aux 2/3 utilitaires essayés, au moins, celui-ci est 100% gratuit, ne plante pas (!) et est donc meilleur pour la planète ;-))
Enfin, franchement, coté utilisation on perd moins de temps à taper la commande dans un shell DOS "à l'ancienne" que d'utiliser une interface fenêtrée et 36 clics à refaire à chaque fois...

Au fait, à quoi ça sert de changer la dates d'un fichier ?
C'est vrai, c'est assez rare... mais voici quelques exemples :
- remettre les photos numériques à la date de la prise de vue (en lisant les infos EXIF)
- taguer un ensemble de fichiers dans un répertoire
- corriger des problèmes de compilation (si la date du fichier projet est plus récente que celle des fichiers .c/.c++, il n'y a plus de compilation incrémentale)
- etc.

Source / Exemple :


// FTouch.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// The one and only application object

CWinApp theApp;

using namespace std;

CString	g_sFileFilter;
CTime	g_aTime=0;
CTime	g_mTime=0;
CTime	g_cTime=0;
BOOL	g_bRecurseDirs=FALSE;

// Output date format
const char g_szFmt[] = "%Y/%m/%d %H:%M:%S";

// Find a substring in a string without case sensitivity
LPCTSTR stristr( LPCTSTR string, LPCTSTR strCharSet )
{
	char *pStr=NULL;
	char szUpBuffer[1024]={0};
	char szUpCharSet[127]={0};
	UINT i;
	for (i=0;i<strlen(string    )+1;i++) szUpBuffer [i]=(char)toupper(string[i]);
	for (i=0;i<strlen(strCharSet)+1;i++) szUpCharSet[i]=(char)toupper(strCharSet[i]);
	pStr = strstr( szUpBuffer, szUpCharSet );
	if (!pStr) return NULL;
	i=pStr-szUpBuffer;
	pStr=(char *)string+i;
	return pStr;
}

// Function that tests 
BOOL FileMatch(LPCTSTR pszFileName, LPCTSTR pszFileFilter)
{
	int i0,i1,n0,n1,n;

	char szDrive[2][_MAX_DRIVE]={0};
	char szDir  [2][_MAX_DIR  ]={0};
	char szName [2][_MAX_FNAME]={0};
	char szExt  [2][_MAX_EXT  ]={0};
	char szPath [_MAX_PATH ]={0};
	LPCTSTR p=NULL;

	_splitpath(pszFileName  , szDrive[0], szDir[0], szName[0], szExt[0]);
	_splitpath(pszFileFilter, szDrive[1], szDir[1], szName[1], szExt[1]);
	// Remove the unnecessary point separator
	if (szExt[0][0]=='.') strcpy(&szExt[0][0],&szExt[0][1]);
	if (szExt[1][0]=='.') strcpy(&szExt[1][0],&szExt[1][1]);

	// Check if directories match
	i0=0; n0=strlen(szDir[0]);
	i1=0; n1=strlen(szDir[1]);
	n=max(n0,n1);
	p=stristr(szDir[0],szDir[1]);
	if (p) i0=p-szDir[0];
	if (n1>0) while (i0<n && i1<n)
	{
		char c0=toupper(szDir[0][i0]);
		char c1=toupper(szDir[1][i1]);
		switch (c1)
		{
			case '*' :	i0=n ; i1=n ;	break;
			case '?' :	i0++ ; i1++ ;	break;
			default  :	if (c0!=c1) return FALSE;
						i0++ ; i1++;	break;
		}
	}

	// Check if names match
	i0=0; n0=strlen(szName[0]);
	i1=0; n1=strlen(szName[1]);
	n=max(n0,n1);
	while (i0<n && i1<n)
	{
		char c0=toupper(szName[0][i0]);
		char c1=toupper(szName[1][i1]);
		switch (c1)
		{
			case '*' :	i0=n ; i1=n ;	break;
			case '?' :	i0++ ; i1++ ;	break;
			default  :	if (c0!=c1) return FALSE;
						i0++ ; i1++;	break;
		}
	}

	// Check if extensions match
	i0=0; n0=strlen(szExt[0]);
	i1=0; n1=strlen(szExt[1]);
	n=max(n0,n1);
	while (i0<n && i1<n)
	{
		char c0=toupper(szExt[0][i0]);
		char c1=toupper(szExt[1][i1]);
		switch (c1)
		{
			case '*' :	i0=n ; i1=n ;	break;
			case '?' :	i0++ ; i1++ ;	break;
			default  :	if (c0!=c1) return FALSE;
						i0++ ; i1++;	break;
		}
	}
	return TRUE;
}

// Base function that changes the date stamps of a file
BOOL Touch(LPCTSTR pszPath)
{
	CFileStatus status;
	CFile::GetStatus(pszPath,status);
	if (g_aTime>0) status.m_atime = g_aTime;
	if (g_cTime>0) status.m_ctime = g_cTime;
	if (g_mTime>0) status.m_mtime = g_mTime;
	CFile::SetStatus(pszPath,status);
	TRACE("c:%s m:%s a:%s \t%s\n",
		status.m_ctime.Format(g_szFmt),
		status.m_mtime.Format(g_szFmt),
		status.m_atime.Format(g_szFmt), pszPath);
	return TRUE;
}

// Recursive function that changes the date stamps of files
// matching the file filter. Returns the number of files touched
int TouchFiles(LPCTSTR pszPath,LPCTSTR pszFilter)
{
	int n=0;
	
	if (!SetCurrentDirectory(pszPath))
		return n;

	CFileFind FileFind;
	if ( !FileFind.FindFile("*.*") )
		return n;

	BOOL bNext;
	do
	{
		bNext = FileFind.FindNextFile();
		if (FileFind.IsDots()) continue;
		CString sPath = FileFind.GetFilePath();
		// On ajoute les fichier à la liste des fichiers
		if (FileFind.IsDirectory())
		{
			if (g_bRecurseDirs)
				n+= TouchFiles(sPath,pszFilter);
		}
		else
		{
			if (!FileMatch(sPath,pszFilter))
				continue;
			if (Touch(sPath)) n++;
		}
	} while (bNext);
//	TRACE("%s : %d datafiles found\n",pszPath,n);
	return n;
}

// Utility function that decodes times expressed in the command line
// If error, returns CTime(0) <=> Jan. 1st 1970, 01:00:00
CTime ExtractTime(LPCTSTR pszParam)
{
	CString s = pszParam;

	// Replace all exotic characters by whites
	s.Replace("/"," ");
	s.Replace(":"," ");
	s.Replace("_"," ");
	s.Replace("@"," ");
	s.Replace("  "," ");

	// Take current time as default
	CTime t=CTime::GetCurrentTime();
	int nYear = t.GetYear();
	int nMonth= t.GetMonth();
	int nDay  = t.GetDay();
	int nHour = t.GetHour();
	int nMin  = t.GetMinute();
	int nSec  = t.GetSecond();

	// Break-up the expression with whites
	int n = strlen(s);
	if (!strstr(s," ")) switch(n)
	{
		case 0 : break;
		case 6 :
			s.Insert( 4," ");
			s.Insert( 2," ");
			break;
		case 8 :
			s.Insert( 6," ");
			s.Insert( 4," ");
			break;
		case 12 :
			s.Insert(10," ");
			s.Insert( 8," ");
			s.Insert( 6," ");
			s.Insert( 4," ");
			s.Insert( 2," ");
			sscanf(s,"%d",&n);
			if (n>18 && n<21)	// Are the seconds or the century missing here ? Bug if (year%100)=19 or 20 !!!
				s = s.Left(2)+s.Right(14);
			break;
		case 14 :
			s.Insert(12," ");
			s.Insert(10," ");
			s.Insert( 8," ");
			s.Insert( 6," ");
			s.Insert( 4," ");
			break;
	}

	// Attribute years, months, days, hours, minutes, seconds
	n = strlen(s);
	switch(n)
	{
		case 0 : return CTime(nYear,nMonth,nDay,nHour,nMin,nSec);

		case 1 : return CTime(0);

		case 8 : 
		case 10: 
			n=sscanf(s,"%d %d %d",&nYear,&nMonth,&nDay);
			if (n!=3) return CTime(0);
			break;

		case 14 :
		case 16 :
			n=sscanf(s,"%d %d %d %d %d",&nYear,&nMonth,&nDay,&nHour,&nMin);
			if (n!=5) return CTime(0);
			nSec=0;
			break;

		case 17 :
		case 19 :
			n=sscanf(s,"%d %d %d %d %d %d",&nYear,&nMonth,&nDay,&nHour,&nMin,&nSec);
			if (n!=6) return CTime(0);
			break;

		default :
			return CTime(0);
	}

	// Check validity ranges
	if (nYear>=0 && nYear<1000 && nYear>50) nYear+=1900; else
	if (nYear>=0 && nYear<1000 && nYear<50) nYear+=2000;
	if (nYear < 0 || nYear >3000) return CTime(0);
	if (nMonth<=0 || nMonth>12) return CTime(0);
	if (nDay  <=1 || nDay  >31) return CTime(0);
	if (nHour < 0 || nHour >23) return CTime(0);
	if (nMin  < 0 || nMin  >59) return CTime(0);
	if (nSec  < 0 || nSec  >59) return CTime(0);
	
	// Build the time value to return
	t = CTime(nYear,nMonth,nDay,nHour,nMin,nSec);
	return t;
}

// Function that tests outputs (examples)
CString DateEx(LPCTSTR psz)
{
	CTime t = ExtractTime(psz);
	CString s=StringFormat("%-20s : %s",psz,t.Format(g_szFmt));
	return s;
}

int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
	int		nRetCode = 0;

	// initialize MFC and print and error on failure
	if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0))
	{
		// TODO: change error code to suit your needs
		cerr << _T("Fatal Error: MFC initialization failed") << endl;
		nRetCode = 1;
	}
	else
	{
		char drive[_MAX_DRIVE]={0};
		char dir  [_MAX_DIR  ]={0};
		char fname[_MAX_FNAME]={0};
		char ext  [_MAX_EXT  ]={0};
		char szBuffer[MAX_PATH]={0};

		// Handle command-line flags
		int nArgc = argc; 
		for (int i=argc-1;i>=1;i--)
		{
			LPCTSTR pszArg=argv[i];
			if ( strlen(pszArg)<2 ) continue;
			if ( pszArg[0]!='/' && pszArg[0]!='-' )
			{
				if ( g_sFileFilter=="")	g_sFileFilter = pszArg;
			}
			else
			{
				BOOL bFound=FALSE;
				if ( _strnicmp(&pszArg[1],"A=",2)==0) { g_aTime = ExtractTime(&pszArg[3]); if (g_aTime>0) TRACE("aTime=%s\n",g_aTime.Format(g_szFmt)); } else
				if ( _strnicmp(&pszArg[1],"M=",2)==0) { g_mTime = ExtractTime(&pszArg[3]); if (g_mTime>0) TRACE("mTime=%s\n",g_mTime.Format(g_szFmt)); } else
				if ( _strnicmp(&pszArg[1],"C=",2)==0) { g_cTime = ExtractTime(&pszArg[3]); if (g_cTime>0) TRACE("cTime=%s\n",g_cTime.Format(g_szFmt)); } else
				if ( _stricmp (&pszArg[1],"A"   )==0) { g_aTime = CTime::GetCurrentTime(); TRACE("aTime=%s\n",g_aTime.Format(g_szFmt)); } else
				if ( _stricmp (&pszArg[1],"M"   )==0) { g_mTime = CTime::GetCurrentTime(); TRACE("mTime=%s\n",g_mTime.Format(g_szFmt)); } else
				if ( _stricmp (&pszArg[1],"C"   )==0) { g_cTime = CTime::GetCurrentTime(); TRACE("cTime=%s\n",g_cTime.Format(g_szFmt)); } else
				if ( _stricmp (&pszArg[1],"S"   )==0) { g_bRecurseDirs = TRUE; } else
				if (bFound)
				{
					// Push command-line params
					for (int j=i;j<argc;j++) 
						argv[j] = (j<argc-1) ? argv[j+1] : NULL;
					argc--;
				}
				else
				{
					TRACE("%s : unknown flag !\n",pszArg);
				}
			}
		}

		if (g_sFileFilter!="")
		{
			char szDrive   [_MAX_DRIVE	]={0}; 
			char szDir	   [_MAX_DIR	]={0}; 
			char szName	   [_MAX_FNAME	]={0}; 
			char szExt	   [_MAX_EXT	]={0};
			char szInitFileDir[_MAX_PATH]={0};
			char szFileFilter [_MAX_PATH]={0};

			_splitpath(g_sFileFilter, szDrive, szDir, szName, szExt);
			_makepath (szInitFileDir, szDrive, szDir, NULL, NULL);
			_makepath (szFileFilter , NULL, NULL, szName, szExt);
			if (!szInitFileDir[0]) GetCurrentDirectory(_MAX_PATH,szInitFileDir);
			if (!szFileFilter[0]) strcpy(szFileFilter,"*.*");

			TouchFiles(szInitFileDir,szFileFilter);
		}
		else
		{
			TRACE("File touch utility\n");
			TRACE("\tUsage      : FTouch <filespec> /C /M /A /C=<date> /M=<date> /A=<date> \n");
			TRACE("\tDateFormats: %s\n",DateEx("20061129175900"));
			TRACE("\t             %s\n",DateEx("20061129"));
			TRACE("\t             %s\n",DateEx("061129"));
			TRACE("\t             %s\n",DateEx("2006_11_29"));
			TRACE("\t             %s\n",DateEx("2006_11_29_17_59_00"));
			TRACE("\t             %s\n",DateEx("2006/11/29_17:59"));
			TRACE("\t             %s\n",DateEx("06/11/29_17:59:00"));
		}
	}

#ifdef _DEBUG
	printf("\nPress a key to exit program...");
	getchar();
#endif

	return nRetCode;
}

Conclusion :


Voilà, c'est mon 1er post.
Je le publie juste pour aider les confrères qui ont les mêmes besoins que moi : retrouver l'équivalent du bon vieux "touch.exe"...

Comme vous le verrez, le code n'est pas révolutionnaire, mais simple et de bon-goût.
Aussi, je pense que ce code intéressera plutôt les débutants.
Désolé, les commentaires sont en anglais (contexte professionnel), mais je pense qu'en tant que développeurs, ça ne doit pas être un problème insurmontable...

Ah, dernière chose : je ne me souvenais plus de la syntaxe de la ligne de commande du programme original, alors j'en ai recréée une qui est simple et puissante.
Le programme lancé seul affiche cette syntaxe de ligne de commande.

Bonne lecture et au plaisir !

Codes Sources

A voir également

Vous n'êtes pas encore membre ?

inscrivez-vous, c'est gratuit et ça prend moins d'une minute !

Les membres obtiennent plus de réponses que les utilisateurs anonymes.

Le fait d'être membre vous permet d'avoir un suivi détaillé de vos demandes et codes sources.

Le fait d'être membre vous permet d'avoir des options supplémentaires.