ripsly25
ripsly25

Reputation: 11

Reading a date from a file

I am going to pipe in an input such as 2021/10/7 (Year/Month/Day). I wont be asking the user for input and I need the code to be able to read the input properly. I don't understand how to get input (a date) from Linux and how my program will transverse that input into its proper places inside of my code.

We've gone over <fstream> briefly and ifstream inData; ofstream outData; .open and .close but for my last assignment in the sample solution that was posted freopen was used which I am completely unfamiliar with. I have asked for help from my professor specifically but encourages using outside means like the compute science discord and tutoring which are not that much help.

What I'm looking for is a way to learn how to be able to input the data so that it can be read from the data stream and how I should structure something like this for getting input from Linux instead of the user.

Write a program that reads a date from the standard input stream.

If the date is valid, the program should write the weekday number followed by the English weekday name (e.g., "Monday", "Tuesday", "Wednesday", ..., "Sunday"). Function main returns 0.

If the date read from the standard input stream is invalid, the program should write "Error: invalid date". Function main returns 1.

Sample Interactions

$ echo "2021/03/01" | ./pa04
0 Monday
$ echo $?
0

$ echo "  2022/3/1" | ./pa04
1 Tuesday
$ echo $?
0

$ echo "3/1/2022" | ./pa04
Error: invalid date
$ echo $?
1

$ echo "abracadabra" | ./pa04
Error: invalid date
$ echo $?
1  
#include iostream
#include string
#include iomanip
#include cmath

using namespace std;

int main() {
    int day, month, year, w;

    // When I get here I feel there needs to be a cin.ignore() for the '/' in 2021/10/07. 
    // But I'm also concerned that if someone puts in 7 instead of 07 like in the sample interactions that it will jack up the results.
    cin >> year >> month >> day;

    w = (day + (13 * (month + 1) / 5) + (year) + (year / 4) - (year / 100) + (year / 400)) % 7;

    switch (w) {
        case 1:
            cout << "Sunday \n";
            break;
        case 2:
            cout << "Monday \n";
            break;
        case 3:
           cout << "Tuesday \n";
           break;
        case 4:
            cout << "Wednesday \n";
            break;
        case 5:
            cout << "Thursday \n";
            break;
        case 6:
            cout << "Friday \n";
            break;
        case 7:
            cout << "Saturday \n";
            break;
    }
    
    return 0;
}

Upvotes: 0

Views: 801

Answers (2)

rturrado
rturrado

Reputation: 8064

You can use C++20's std::chrono for this as well. It lets you:

  • Parse an input string, in accord to a format specification, into a time object (e.g. days in a system clock).
  • Get the week day for that time object.

I've found however that the std::chrono's parse method:

  1. Is not very flexible. AFAIK, it doesn't let you use regular expressions (for example, for matching whitespaces at the beginning of the input string, as in one of your input samples,
    2022/4/2; this, in particular, would be easily fixed trimming the string before parsing it).
  2. Can lead to some parsing surprises. For example, for another one of your input samples, 5/3/2023, it will interpret the date as the 20th of March of the year 5. That is, it will match the day to the first 2 digits, 20, of what should be the day in your input string, 2023.

So I would go for a mixed solution here:

  • a manual parsing of the input string into a time object.
  • then using std::chrono for geting the week day corresponding to that time object.

The code below implements both solutions in two different functions, using_just_chrono and using_regex_and_chrono, just to show the issues I mentioned above if we just use std::chrono. I have used regular expressions for the manual parsing but you could just capture the year, month, and day of each date with a string stream after doing some trimming.

#include <chrono>  // parse, sys_days, weekday
#include <iostream>  // cout
#include <regex>  // regex_match, smatch
#include <sstream>  // istringstream

namespace ch = std::chrono;

void using_just_chrono(const std::string& date)
{
    ch::year_month_day ymd{};
    std::istringstream iss{ date };
    if (iss >> ch::parse(std::string{ "%Y/%m/%d" }, ymd) and ymd.ok())
    {
        std::cout << "\t\tusing just chrono: " << ch::weekday{ ch::sys_days{ ymd } } << "\n";
    }
}

void using_regex_and_chrono(const std::string& date)
{
    // Probably more portable using [[:space:]] and [[:digit:]] here
    std::regex pattern{ R"(^\s*(\d{1,4})/(\d{1,2})/(\d{1,2})\s*$)" };
    std::smatch matches{};
    if (std::regex_match(date, matches, pattern))
    {
        ch::year_month_day ymd(
            ch::year{ std::stoi(matches[1]) },
            ch::month{ std::stoul(matches[2]) },
            ch::day{ std::stoul(matches[3]) });
        if (ymd.ok())
        {
            std::cout << "\t\tusing rgx and chr: " << ch::weekday{ ch::sys_days{ ymd } } << "\n";
        }
    }
}

int main()
{
    std::cout << "Parsing dates:\n";
    for (const std::string& date : {
        "2021/03/01",  // OK
        "  2022/4/2",  // OK: needs trimming white spaces at the front
        "5/3/2023",  // wrong: day is 2023
        "abracadabra",  // wrong format
        "2020/12/32",  // wrong: day is 32
        "2019/13/20",  // wrong: month is 13
        "2021/2/29" })  // wrong: non existing date
    {
        std::cout << "\t\"" << date << "\":\n";
        using_just_chrono(date);
        using_regex_and_chrono(date);
    }
    std::cout << "\n";
}

// Outputs:
//
// Parsing dates:
//     "2021/03/01":
//         using just chrono: Mon
//         using rgx and chr: Mon
//     "  2022/4/2":
//         using rgx and chr: Sat
//     "5/3/2023":
//         using just chrono: Sun
//     "abracadabra":
//     "2020/12/32":
//     "2019/13/20":
//     "2021/2/29":

Upvotes: 0

Remy Lebeau
Remy Lebeau

Reputation: 596156

You are not handling the / characters at all.

You can use additional >> calls and variables for that:

int day, month, year;
char slash1, slash2;

if ((cin >> year >> slash1 >> month >> slash2 >> day) &&
    (slash1 == '/') &&
    (slash2 == '/'))
{
    // use values as needed...
}
else
{
    // invalid format...
}

Or, you can use istream::ignore() to skip the / characters:

int day, month, year;

if (cin >> year &&
    cin.ignore(numeric_limits<streamsize>::max(), '/') &&
    cin >> month &&
    cin.ignore(numeric_limits<streamsize>::max(), '/') &&
    cin >> day)
{
    // use values as needed...
}
else
{
    // invalid format...
}

Or, in C++11 and later, you can use std::get_time() to let the standard library read in and parse the date components for you:

#include <iomanip>

tm date;
if (cin >> get_time(&date, "%Y/%m/%d"))
{
    int day = date.tm_mday;
    int month = date.tm_mon + 1;
    int year = date.tm_year + 1900;
    int w = date.tm_wday + 1;
    // use values as needed...
}
else
{
    // invalid format...
}

Using std::get_time() has the added benefit that std::tm has a tm_wday member for the day of the week, as you can see above, so you don't have to calculate it manually.

Upvotes: 1

Related Questions