How to iterate through a date range in c++ - c++

I have a folder with csv files. Each csv file is named by a date (eg. 01JAN2013.csv, 02JAN2013.csv ). I have to read the files in the order of their date (start date and end date are known).
So I am trying to loop through the dates, from the start date to the end date, in order to generate the file names.
Currently I am doing:
vector<string> dd{"01", "02", "03", "04", "05", "06", "07", "08", "09","10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20","21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31"};
vector<string> mmm{"JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG","SEP", "OCT", "NOV", "DEC"};
vector<string> yyyy{"2011","2012","2013","2014"};
string filePath=DataFolderPath;
for(int i=0;i<yyyy.size();i++)
{
for(int j=0;j<mmm.size();j++)
{
for(int k=0;k<dd.size();k++)
{
filePath.append(dd[k]);
filePath.append(mmm[j]);
filePath.append(yyyy[i]);
filePath.append(".csv");
}
}
}
Definitely ugly, but it gets the job done.
Is there an easier way to loop through dates in C++. Something along the lines of:
for ( currentDate = starDate; currentDate < endDate; currentDate++) {
//Do stuff
}
UPDATE:
This is the approach that I finally used, combination of both the answers below:
typdef struct tm Time;
Time startDate=makeDate(3,1,2011);
Time endDate=makeDate(24,1,2014);
time_t end=mktime(&endDate);
for(Time date=startDate;end>=mktime(&date);++date.tm_mday)
{
char buffer[16];
strftime(buffer, sizeof(buffer), "%d%b%Y.csv", &date);
std::string filename(buffer);
//To convert month to CAPS
std::transform(filename.begin()+2, filename.begin()+5,filename.begin()+2, ::toupper);
std::cout << filename << "\n";
}
I also used a makeDate helper function based on paddy's answer which return a struct tm instead of time_t:
Time makeDate( int day, int month, int year )
{
Time ttm = {0};
ttm.tm_mday= day;
ttm.tm_mon= month-1;
ttm.tm_year= year-1900;
return ttm;
}

It might not actually be easier.... But you can use the time functions from <ctime>. Something like this:
string MakeFileName( time_t t )
{
static const char* months[] = { "JAN", "FEB", "MAR", "APR", "MAY", "JUN",
"JUL", "AUG", "SEP", "OCT", "NOV", "DEC" };
struct tm *ptm = localtime(&t);
char buffer[20];
snprintf( buffer, 20, "%02d%s%04d.CSV",
ptm->tm_day, months[ptm->tm_month], ptm->tm_year+1900 );
return string(buffer);
}
Now just get your start and end dates correct in a time_t value (remember, it's in seconds). You can just use 00:00:00 for the time.
// Note that day and month are 1-based.
time_t GetDate( int day, int month, int year )
{
struct tm ttm = {0};
ttm.tm_day = day;
ttm.tm_month = month-1;
ttm.tm_year = year-1900;
return mktime(&ttm);
}
With that helper you can set your start and end dates easily:
time_t start = GetDate(1, 1, 2011);
time_t end = GetDate(28, 10, 2013);
for( time_t t = start; t <= end; t += 86400 )
{
string filename = MakeFileName(t);
// TODO...
}

If you decide to generate the names, I think #paddy has the right general idea, but the implementation may be open to a little improvement. In particular, mktime not only does conversions, it can...fix its input, so if you give it an input of 30 Feb, it knows that's either 1 or 2 March, and will change it appropriately. Likewise, it knows that 32 December 2011 is really 1 Jan 2012 and (again) adjusts the input appropriately.
At a guess, you also probably don't really want to generate dates into the future, and just had it generating dates through the end of the year because you didn't want to modify it every date. I'm guessing just generating dates up through the date it's run is probably sufficient.
With those in mind, I'd write the code more like this:
#include <time.h>
#include <iostream>
int main() {
struct tm date;
date.tm_mon = 1;
date.tm_mday = 1;
date.tm_year = 2011 - 1900;
time_t end = time(NULL);
for (; mktime(&date) < end; ++date.tm_mday) {
char buffer[16];
strftime(buffer, sizeof(buffer), "%d%b%Y", &date);
std::cout << buffer << "\t";
}
}
For the moment this just prints out the strings, but with them generated correctly, using them as file names will normally be pretty trivial.
If you want to do as I suggested in the comment, and get all the file names, then sort them into order by date, you can convert each file name to a time_t, the sort the time_ts. One fairly easy way to do that is with the std::get_time manipulator:
time_t cvt(std::string const &filename) {
std::istringstream in(filename);
struct tm date;
in >> std::get_time(&date, "%d%b%Y");
return mktime(date);
}
With this, you could (for one possibility) put the time_ts and file names into a std::map, then just walk through the map from beginning to end, and process each file in order.
std::map<time_t, std::string> files;
std::string file_name;
while (get_file_name(&file_name))
files[cvt(file_name)] = file_name;
for (auto const &f : files)
process(f.second);

#include <boost/date_time.hpp>
using namespace boost::gregorian;
long dateDifference( string start_date, string end_date )
{
date _start_date(from_simple_string(start_date));
date _end_date(from_simple_string(end_date));
for(day_iterator iter = _start_date; iter!=_end_date; ++iter)
{
// Do what you want
}
}

Related

Parsing Subsecond Date with Howard Hinnant Date Library

I have a date string like so YYYYMMDD HHMMSSFFF. I am trying to use Howard Hinnats date library. Snippet of code is like so,
std::chrono::system_clock::time_point tp;
char date[20] = {0};
std::istringstream ss{date};
ss >> date::parse("%Y%m%d %H%M%S%s", tp);
long ts = (std::chrono::time_point_cast<std::chrono::nanoseconds>(tp)
.time_since_epoch() /
std::chrono::nanoseconds(1));
But this code isn't reading the subsecond FFF. I loooked on the documentation here and it states that %s represents fractional of a second time. An example value for date is 20170110 103648340. But when I output ts I get 0. If you are wondering why I convert to nanoseconds its because I need the date in nanoseconds for other operations.
Use %T, it seems to work. Here is an example:
#include <date/date.h>
int main()
{
std::string dt{ "20190501 113001234" };
dt = dt.insert(11, ":");
dt = dt.insert(14, ":");
dt = dt.insert(17, ".");
// now we have "20190501 11:30:01.234"
std::chrono::system_clock::time_point tp;
std::istringstream ss{ dt };
ss >> date::parse("%Y%m%d %T", tp);
long ts = (std::chrono::time_point_cast<std::chrono::nanoseconds>(tp)
.time_since_epoch() /
std::chrono::nanoseconds(1));
}
You could also parse it this way:
sys_seconds tp;
int ims;
ss >> parse("%Y%m%d %H%M%2S", tp) >> ims;
return tp + milliseconds{ims};
The %2S says: parse as much as 2 chars for the seconds. That leaves the trailing three digits yet to be parsed. Pick those up with a integral parse and convert that integer to milliseconds, and you're good.
This won't work if there are trailing digits after the 3 millisecond digits.

looking for another time structure with member objects other than tm

the member tm_mon, in struct tm is stored as an integer. I'm looking for another time stuct that stores the actual name of the month. I can get the user-friendly format with
ctime();
but how can I selectively output just the month?
Have an array like,
string Months[] = {"January", "February", ... };
Then when you want to print use,
time_t t = time(0); // get time now
struct tm * now = localtime( & t );
cout << Months[now-> tm_mon];

Get System Date Format String Win32

I need to get the system current date format string like ("dd-mm-yyyy" , "mm/dd/yyyy" ect.
GetDateFormat() API returns the formatted string like "12-09-2015" but need string like "dd-mm-yyyy"
C# solution
string sysFormat = CultureInfo.CurrentCulture.DateTimeFormat.ShortDatePattern;
But I need in Win32.
You can get a list of format strings currently applicable, by enumerating them. This is done through EnumDateFormats. Note, that there can be (and usually is) more than one, so you will have to decide, which one to pick1).
The following code returns the system's default short dateformat pattern:
static std::list<std::wstring> g_DateFormats;
BOOL CALLBACK EnumDateFormatsProc( _In_ LPWSTR lpDateFormatString ) {
// Store each format in the global list of dateformats.
g_DateFormats.push_back( lpDateFormatString );
return TRUE;
}
std::wstring GetShortDatePattern() {
if ( g_DateFormats.size() == 0 &&
// Enumerate all system default short dateformats; EnumDateFormatsProc is
// called for each dateformat.
!::EnumDateFormatsW( EnumDateFormatsProc,
LOCALE_SYSTEM_DEFAULT,
DATE_SHORTDATE ) ) {
throw std::runtime_error( "EnumDateFormatsW" );
}
// There can be more than one short date format. Arbitrarily pick the first one:
return g_DateFormats.front();
}
int main() {
const std::wstring strShortFormat = GetShortDatePattern();
return 0;
}
1) The .NET implementation does the same thing. From its list of candidates, it arbitrarily picks the first one.
You can use time function together with localtime function.
Example code:
//#include <time.h>
time_t rawtime;
struct tm * timeinfo;
time(&rawtime);
timeinfo = localtime(&rawtime);
//The years are since 1900 according the documentation, so add 1900 to the actual year result.
char cDate[255] = {};
sprintf(cDate, "Today is: %d-%d-%d", timeinfo->tm_mday, timeinfo->tm_mon, timeinfo->tm_year + 1900);

Splitting a `char[]` in C++

I have a char[] in YYYYMMDDHHMMSS format.
e.g. 2011052504572
I want to retrieve the year, month, date, hour, minute and second from this char. How do I do that?
NOTE:I cant use any third party dll.
Thanks,
Syd
If you're using the STL then just put the string into a std::string and use the substr method:
std::string dateTime=......;
std::string year=dateTime.substr(0,4);
std::string month=dateTime.substr(4,2);
// etc
// etc
Use string::substr() for this purpose. Example,
string date = "20110524112233";
string year = date.substr(0, 4);
string month = date.substr(4, 2);
string day = date.substr(6, 2);
string hour = date.substr(8, 2);
string minute = date.substr(10, 2);
string second = date.substr(12, 2);
It depends on whether you want to extract the values as text, or convert them to numbers. For getting lots of strings, you can use std::string and substr() as thoroughly illustrated in other answers.
If you want to get numbers that you can then calculate with, then one approach is:
int year, month, day, hour, minute, second;
if (sscanf(input, "%.4d%.2d%.2d%.2d%.2d%.2d",
&year, &month, &day, &hour, &minute, &second) == 6)
{
// all 6 conversions worked... can use the values...
int second_in_day = hour * 3600 + minute * 60 + second;
...
}
Another approach is to use strptime() - if your system has it. It parses a string into a broken-down-time structure:
struct tm tm;
strptime(input, "%Y%m%d%H%M%S", &tm);
// parsed values are in tm.tm_year, tm.tm_mon, tm.tm_mday,
// tm.tm_hour, tm.tm_min, tm.tm_sec
// further, tm_wday has day of week, tm_yday has day in year
// i.e. it actually understands the date, not just chopping up numbers/text
Note: sscanf() and strncpy() are C functions callable from C++, and they're not as safe to use as C++-specific functionality (std::string, std::istringstream) in that small misunderstandings and mistakes in handling the data can lead to not just erroneous results, but program crashes. So, read the manual pages for these things carefully if you use them.
Use strncpy... okay, so if it is not homework, then this is the best way. The others using std::string are wasting resources:
static const char *yyyymmddhhmmss = "20110526101559";
char year[5] = { '\0' };
char month[3] = { '\0' };
char day[3] = { '\0' };
char hour[3] = { '\0' };
char minute[3] = { '\0' };
char second[3] = { '\0' };
strncpy(year, yyyymmddhhmmss, 4);
strncpy(month, &yyyymmddhhmmss[4], 2);
strncpy(day, &yyyymmddhhmmss[6], 2);
strncpy(hour, &yyyymmddhhmmss[8], 2);
strncpy(minute, &yyyymmddhhmmss[10], 2);
strncpy(second, &yyyymmddhhmmss[12], 2);
Since you have the input in the form of a char[] (or char*—for
this use, it comes out to the same thing), the simplest solution is
probably using the two iterator constructors for std::string to creat
a string for each field, e.g.:
std::string year ( date , date + 4 );
std::string month( date + 4, date + 6 );
std::string day ( date + 6, date + 8 );
// ...
If you need the numerical values, boost::lexical_cast can be used,
e.g.:
int extractField( char const* string, int begin, int end )
{
return boost::lexical_cast<int>(
std::string( date + begin, date + end ) );
}
int year = extractField( date, 0, 4 );
int year = extractField( date, 4, 6 );
// ...

How to convert a string to datetime in C++

I have a resultset(from a function) which is based on time. But the datetime value is in string format(e.g. "21:5 Jan 23, 11"). I want to convert "21:5 Jan 23, 11" to datetime. How can I do this in C++? I just want to filter records for today. So i need to retrieve the current date from "21:5 Jan 23, 11".
Edit:
I can get the current date and time using
SYSTEMTIME st;
GetSystemTime(&st);
Is there any way to convert "21:5 Jan 23, 11" in the above format?
#include <ctime>
#include <iomanip>
#include <iostream>
#include <sstream>
// Converts UTC time string to a time_t value.
std::time_t getEpochTime(const std::wstring& dateTime)
{
// Let's consider we are getting all the input in
// this format: '2014-07-25T20:17:22Z' (T denotes
// start of Time part, Z denotes UTC zone).
// A better approach would be to pass in the format as well.
static const std::wstring dateTimeFormat{ L"%Y-%m-%dT%H:%M:%SZ" };
// Create a stream which we will use to parse the string,
// which we provide to constructor of stream to fill the buffer.
std::wistringstream ss{ dateTime };
// Create a tm object to store the parsed date and time.
std::tm dt;
// Now we read from buffer using get_time manipulator
// and formatting the input appropriately.
ss >> std::get_time(&dt, dateTimeFormat.c_str());
// Convert the tm structure to time_t value and return.
return std::mktime(&dt);
}
I'm not a C++ programmer but in C you can use strptime http://pubs.opengroup.org/onlinepubs/009695399/functions/strptime.html
The most general C++ way is to use Boost.DateTime.
This is sample program to validate input datetime and parse
#include <iostream>
#include <comutil.h>
#include <iomanip>
bool ValidateAndParseDateTime(const std::wstring & strInputDate, std::tm & date)
{
bool bSuccess = false;
if (!strInputDate.empty())
{
_variant_t varIn = strInputDate.c_str();
if (SUCCEEDED(VariantChangeTypeEx(&varIn, &varIn, GetThreadLocale(), 0, VT_DATE)))
{
std::get_time(&date, strInputDate.c_str());
bSuccess = true;
}
}
}
int main()
{
std::tm date;
std::wstring strInputDate = L"7/20/2020 1:29:37 PM";
if (!ValidateAndParseDateTime(strInputDate, date))
{
//Invalid date and time
}
return 0;
}
If all you need is to check whether two strings have same date or not and if it is guaranteed that strings are in the same format, then there is no need to convert it to date time. You just have to compare the substrings after the first space character. If they are same, then the dates are same. Here is the sample code:
using namespace std;
string getCurrentDate()
{
//Enumeration of the months in the year
const char* months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
//Get the current system date time
SYSTEMTIME st;
GetSystemTime(&st);
//Construct the string in the format "21:5 Jan 23, 11"
ostringstream ss;
ss<<st.wHour<<":"<<st.wMinute<<
" "<<months[st.wMonth-1]<<
" "<<st.wDay<<", "<<st.wYear%1000;
//Extract the string from the stream
return ss.str();
}
string getDateString(const string& s)
{
//Extract the date part from the string "21:5 Jan 23, 11"
//Look for the first space character in the string
string date;
size_t indx = s.find_first_of(' ');
if(indx != string::npos) //If found
{
//Copy the date part
date = s.substr(indx + 1);
}
return date;
}
bool isCurrentDate(const string& s1)
{
//Get the date part from the passed string
string d1 = getDateString(s1);
//Get the date part from the current date
string d2 = getDateString(getCurrentDate());
//Check whether they match
return ! d1.empty() && ! d2.empty() && d1 == d2;
}
int main( void )
{
bool s = isCurrentDate("21:5 Jan 23, 11");
bool s1 = isCurrentDate("21:5 Jan 25, 11");
return 0;
}
In the function "ValidateAndParseDateTime" above, I think it's supposed to return "bSuccess". Without it, caller will always get "Invalid date and time".
{