Recently i'm trying to write a program in C++ that reads ZIP files.
One of the properties of a local file header is last modification date
and last modification time. I found in a ZIP file format specification
that "The date and time are encoded in standard MS-DOS format". And
here's my question: how do i decode it? I was trying to find some
information about in the internet, but i can't find anything useful,
so please, if any of you could help me in any way, i would really
appreciate it.
Here's an exapmle of what i'm trying to achieve:
MY PROGRAM:
Last mod file time: 7423 (hex)
Last mod file date: 3658 (hex)
7-ZIP:
Modified: 2007-02-24, 14:33:06
RBIL (Ralf Brown's Interrupt List) via DJGPP website:
http://www.delorie.com/djgpp/doc/rbinter/it/65/16.html
http://www.delorie.com/djgpp/doc/rbinter/it/66/16.html
http://www.delorie.com/djgpp/doc/rbinter/it/52/13.html
http://www.delorie.com/djgpp/doc/rbinter/ix/
RBIL is also available on the web in html from another site and from Mr.
Brown's website as a collection of files.
InfoZip has PKZIP compatible compressors/decompressors with open source.
I.e., you can check what they did.
http://www.info-zip.org/
Let's attempt to manually decode a value using description at links above:
> Last mod file time: 7423 (hex)
bits 15-11 1110 14
bits 5-10 100001 33
bits 0-4 00011 3
Since the lower four bits are /2, 14:33:06...
In C, you might (lots of options...) do something like this (untested):
dos_seconds=(dos_time&0x1F)*2;
dos_minutes=(dos_time>>5)&0x3F;
dos_hours=(dos_time>>11);
sprintf(dos_time_string,"%02d:%02d:%02d",dos_hours,dos_minutes,dos_seconds);
The year-month-day is similar except the size of the two upper fields will
be different, so you'll need different shifts and hex and-constants. You'll
also have to add 1980 to the year.
Rod Pemberton
cout << "Last mode file time: ";
cout << dec << setw(2) << (zip.LastModFileTime >> 11) << ":";
cout << setw(2) << ((zip.LastModFileTime >> 5) & 0x3F) << ":";
cout << setw(2) << (zip.LastModFileTime & 0x1F) * 2 << endl;
cout << dec << (zip.LastModFileDate & 0x1F) << ".";
cout << ((zip.LastModFileDate >> 5) & 0x0F) << ".";
cout << ((zip.LastModFileDate >> 9) & 0x7F) + 1980 << endl;
OUTPUT:
Last mod file time: 14:33:06
Last mod file date: 24.02.2007
It's simple and efficient so double jackpot :) Thanks again Rod :)
Two consecutive words, or a longword, YYYYYYYMMMMDDDDD hhhhhmmmmmmsssss
YYYYYYY is years from 1980 = 0
sssss is (seconds/2).
3658 = 0011 0110 0101 1000 = 0011011 0010 11000 = 27 2 24 = 2007-02-24
7423 = 0111 0100 0010 0011 - 01110 100001 00011 = 14 33 2 = 14:33:06
Note that the MSB changes 2043/4.
--
(c) John Stockton, nr London, UK. ?@merlyn.demon.co.uk Turnpike v6.05.
Web <URL:http://www.merlyn.demon.co.uk/> - w. FAQish topics, links, acronyms
PAS EXE etc : <URL:http://www.merlyn.demon.co.uk/programs/> - see 00index.htm
Dates - miscdate.htm moredate.htm js-dates.htm pas-time.htm critdate.htm etc.
>In C, you might (lots of options...)
Excellent post! Yes lots of options...
// ------------------------------------------------------------------------
// System : Win9X/NT
// Program : touch.c
// Description : modify file times
// handles long filenames under Win9X/NT
// Written by : Bill Buckels
// Date Written : March 2000
// Revision : 1.0 First Release
// Errata : Used Workarounds for incorrect adjustment of localtime
// For some reason the conversion from clock date to
// file date is out by an hour... and I don't completely
// understand why. But if workarounds are OK then
// no plan to fix.
// ------------------------------------------------------------------------
#include <stdlib.h>
#include <stdio.h>
#include <io.h>
#include <string.h>
#include <direct.h>
#include <time.h>
#include <sys/types.h>
#include <sys/utime.h>
#include <sys/stat.h>
/* global vars. */
struct tm TouchTime; /* global structure */
char *timestr = "29:59:59", /* verification pattern */
*alttime = "29:59",
*datestr = "19-39-2999",
*altdate = "19-39-99",
*timearg = "HH:MM:SS", /* command line args - intial values */
*datearg = "MM-DD-CCYY",
searcharg[_MAX_PATH] = "*.*"; /* file spec - initial value */
int force = 0,
nocreate = 0,
verbose = 0,
TouchNow = 0; /* if this flag is set, the filetime is now */
/* positions in the date string MM-DD-CCYY or HH:MM:SS */
#define POS_1 0
#define POS_2 3
#define POS_3 6
/* get the command line options */
/* assign the values to the globals */
int GetOptions(char *arg)
{
int len,
idx,
val,
found = 0;
char c;
/* parse switches */
if (arg[0] == '-') {
for (idx = 0; arg[idx] != 0; idx++) {
c = tolower(arg[idx]);
switch(c) {
case 'f': force = 1; break; /* force readonly */
case 'c': nocreate = 1; break; /* don't create 0 length */
case 'v': verbose = 1; break; /* verbose mode */
}
}
return (found);
}
len = strlen(arg);
/* check for time pattern HH:MM:SS */
if (len == strlen(timestr)) {
found = 1;
for (idx = 0; arg[idx] != 0; idx++) {
if (timestr[idx] == ':' && arg[idx] == ':')
continue;
if (arg[idx] >= '0' && arg[idx] <= timestr[idx])
continue;
found = 0;
break;
}
if (found) {
strcpy(timearg, arg);
TouchTime.tm_hour = atoi(&timearg[POS_1]);
TouchTime.tm_min = atoi(&timearg[POS_2]);
TouchTime.tm_sec = atoi(&timearg[POS_3]);
return found;
}
}
/* check for alternate time pattern HH:MM */
if (len == strlen(alttime)) {
found = 1;
for (idx = 0; arg[idx] != 0; idx++) {
if (alttime[idx] == ':' && arg[idx] == ':')
continue;
if (arg[idx] >= '0' && arg[idx] <= alttime[idx])
continue;
found = 0;
break;
}
if (found) {
TouchTime.tm_hour = atoi(&arg[POS_1]);
TouchTime.tm_min = atoi(&arg[POS_2]);
TouchTime.tm_sec = 0;
sprintf(timearg, "%002d:%002d:%002d",
TouchTime.tm_hour, TouchTime.tm_min, TouchTime.tm_sec);
return found;
}
}
/* check for date pattern - MM-DD-CCYY */
if (len == strlen(datestr)) {
found = 1;
for (idx = 0; arg[idx] != 0; idx++) {
if (datestr[idx] == '-' && arg[idx] == '-')
continue;
if (arg[idx] >= '0' && arg[idx] <= datestr[idx])
continue;
found = 0;
break;
}
if (found) {
strcpy(datearg, arg);
TouchTime.tm_mon = atoi(&datearg[POS_1]) - 1;
TouchTime.tm_mday = atoi(&datearg[POS_2]);
TouchTime.tm_year = atoi(&datearg[POS_3]) - 1900;
return found;
}
}
/* check for alternate date pattern MM-DD-YY */
if (len == strlen(altdate)) {
found = 1;
for (idx = 0; arg[idx] != 0; idx++) {
if (altdate[idx] == '-' && arg[idx] == '-')
continue;
if (arg[idx] >= '0' && arg[idx] <= altdate[idx])
continue;
found = 0;
break;
}
if (found) {
TouchTime.tm_mon = atoi(&arg[POS_1]) - 1;
TouchTime.tm_mday = atoi(&arg[POS_2]);
TouchTime.tm_year = atoi(&arg[POS_3]);
/* no file times in DOS before 1980 - use fixed date window of 1980 */
/* for Y2K rollover... */
if (TouchTime.tm_year < 80)
TouchTime.tm_year += 100;
sprintf(datearg, "%002d-%002d-%4d",
TouchTime.tm_mon + 1, TouchTime.tm_mday, TouchTime.tm_year + 1900);
return found;
}
}
/* usually assume arg is a filename unless it's a date or a time */
/* in this day and age almost anything goes for filenames */
if (len < 3 || (arg[2] != ':' && arg[2] != '-')) {
found = 1;
strcpy(searcharg, arg);
}
return (found);
}
/* function Touch() - the crux of the biscuit */
/* uses the _utime function to modify file times */
/* In DOS we used the _dos_setftime function to modify the file time
after opening a file. In Windows we can do basically the same thing
with the SetFileTime API function. But we don't want to use Windows
calls since this is a console application and we can't use DOS
calls since DOS is dead and didn't handle long names anyways.
Fortunately the Unix Style _utime function offers us a way to modify
the file times without even opening the file.
The _utime function sets the modification time for the file specified
by filename. The process must have write access to the file in order
to change the time. Under Windows NT and Windows 95, you can change
the access time and the modication time in the _utimbuf structure. If
times is a NULL pointer, the modification time is set to the current
local time. Otherwise, times must point to a structure of type
_utimbuf, defined in SYS\UTIME.H.
The _utimbuf structure stores file access and modification times used
by _utime to change file-modification dates. The structure has the
following fields, which are both of type time_t:
actime
Time of file access
modtime
Time of file modification
_utime is identical to _futime except that the filename argument of
_utime is a filename or a path to a file, rather than a handle to an
open file.
_wutime is a wide-character version of _utime; the filename argument
to _wutime is a wide-character string. These functions behave
identically otherwise.
*/
int Touch(unsigned char *fname, time_t time_access, time_t time_write)
{
struct _utimbuf timbuf, *ptimbuf = NULL; /* structure for call to _utime
*/
struct tm *worktime, OldTime;
time_t worktime_t;
int status = 0,
readonly = 0;
/* sometimes we will need to reset the filedate without resetting the time
*/
/* and sometimes we will need to reset the filetime without the date */
/* so we need to save the original file date in case this occurs and */
/* reset only the portion that the user has requested */
worktime = localtime(&time_write);
memcpy(&OldTime, worktime, sizeof(struct tm));
/* preserve orginal time or date if no args */
if (strcmp(timearg, "HH:MM:SS") == 0) {
TouchTime.tm_hour = OldTime.tm_hour;
TouchTime.tm_min = OldTime.tm_min;
TouchTime.tm_sec = OldTime.tm_sec;
}
if (strcmp(datearg, "MM-DD-CCYY") == 0) {
TouchTime.tm_year = OldTime.tm_year;
TouchTime.tm_mon = OldTime.tm_mon;
TouchTime.tm_mday = OldTime.tm_mday;
}
/* build the new time */
/* incorrect behaviour if date and time were current time */
/* daylight saving time was not adjusting properly in mktime */
/* out by an hour - so cludge required */
/* now using a NULL _timbuf structure if setting to current time */
if (TouchNow == 0) {
timbuf.modtime = timbuf.actime = mktime(&TouchTime);
ptimbuf = (struct _utimbuf *)&timbuf;
}
if (force) {
/* if readonly try to set write mode before touching */
/* save old mode to reset readonly after touching */
readonly = _access(fname, 2);
if (readonly) {
_chmod(fname, _S_IWRITE);
}
}
if( _utime( fname, ptimbuf ) == -1 )
status = 0; /* _utime failed so return 0 */
else
status = 1;
if (force && readonly) {
_chmod(fname, _S_IREAD); /* try to force old mode back for read only */
}
if (status && verbose) {
printf("%002d:%002d:%002d ",
TouchTime.tm_hour, TouchTime.tm_min, TouchTime.tm_sec);
printf("%002d-%002d-%4d ",
TouchTime.tm_mon + 1, TouchTime.tm_mday, TouchTime.tm_year + 1900);
printf("%s\n", fname);
}
return status;
}
void main(int argc, char **argv)
{
int idx,
found = 0;
unsigned char path_buffer[_MAX_PATH],
drive[_MAX_DRIVE],
dir[_MAX_DIR],
rootname[_MAX_FNAME],
ext[_MAX_EXT],
buf[11]; /* work buffer for date and time */
long findhandle;
struct _finddata_t wild_card;
time_t now_t;
struct tm *today, /* work structures for date and time */
CurrentTime;
FILE *fp;
puts("Touch(C) v1.0 CopyLeft Bill Buckels 2000. All Rights Reversed.");
if(argc < 2) {
puts("Usage is : \"touch -cfv mm-dd-ccyy hh:mm:ss FileName*.*\"");
puts("- or - : \"touch -cfv mm-dd-yy hh:mm FileName*.*\"");
exit (1);
}
/* parse the command line */
for (idx = 1; idx < argc; idx++) {
GetOptions(argv[idx]);
}
/* if no date and time have been entered at all, use current time */
/* also we set the filetime slightly differently because wierd */
/* behaviour cause conversion to be out by an hour in some cases */
if (strcmp(timearg, "HH:MM:SS") == 0 &&
strcmp(datearg, "MM-DD-CCYY") == 0) {
time(&now_t);
today = localtime(&now_t);
memcpy(&CurrentTime, today, sizeof(struct tm));
sprintf(buf, "%002d:%002d:%002d",
CurrentTime.tm_hour, CurrentTime.tm_min, CurrentTime.tm_sec);
GetOptions(buf);
sprintf(buf, "%002d-%002d-%4d",
CurrentTime.tm_mon + 1, CurrentTime.tm_mday, CurrentTime.tm_year +
1900);
GetOptions(buf);
TouchNow = 1;
}
else {
/* make sure that our time conversion does not suffer from */
/* ansi time conversion and daylight time consideration */
/* use the microsoft defaults with daylight saving disabled */
/* otherwise the time flips by an hour forward or back in some cases */
_putenv("TZ=PST8"); /* affects the program environment only */
_tzset(); /* reverts back to previous settings when done */
}
/* display the command-line options */
printf("SearchArg: %s\n", searcharg);
if (strcmp(timearg, "HH:MM:SS") != 0)
printf("TimeArg : %s\n", timearg);
if (strcmp(datearg, "MM-DD-CCYY") != 0)
printf("DateArg : %s\n", datearg);
puts("");
/* create file if it does not exist unless -c option */
if((findhandle = _findfirst(searcharg, &wild_card)) < 1l)
{
if (nocreate)
exit(0);
fp = fopen(searcharg, "wb");
if (NULL != fp) {
fclose(fp);
if (TouchNow) /* exit if filetime is now */
exit(0);
if((findhandle = _findfirst(searcharg, &wild_card)) < 1l)
exit(1);
}
else {
perror(searcharg);
exit(1);
}
}
strcpy(path_buffer, searcharg); // allow pathing...
_splitpath(path_buffer, drive, dir, rootname, ext);
if(!(wild_card.attrib &_A_SUBDIR))
{
sprintf(path_buffer, "%s%s%s", drive, dir, wild_card.name);
found += Touch(path_buffer, wild_card.time_access,
wild_card.time_write);
}
while(_findnext(findhandle, &wild_card) == 0)
{
if(!(wild_card.attrib &_A_SUBDIR))
{
sprintf(path_buffer, "%s%s%s", drive, dir, wild_card.name);
found += Touch(path_buffer, wild_card.time_access,
wild_card.time_write);
}
}
_findclose(findhandle);
if(!found)
{
puts("No files touched...");
exit (1);
}
exit(0);
}