2020-01-18 11:38:21 +03:00
|
|
|
/*
|
|
|
|
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
2023-03-12 20:21:57 +03:00
|
|
|
* Copyright (c) 2023, Karol Baraniecki <karol@baraniecki.eu>
|
2020-01-18 11:38:21 +03:00
|
|
|
*
|
2021-04-22 11:24:48 +03:00
|
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
2020-01-18 11:38:21 +03:00
|
|
|
*/
|
|
|
|
|
2023-04-05 18:20:13 +03:00
|
|
|
#include <AK/DateConstants.h>
|
2023-03-12 20:21:57 +03:00
|
|
|
#include <AK/Find.h>
|
2023-03-12 11:45:24 +03:00
|
|
|
#include <AK/String.h>
|
|
|
|
#include <AK/StringBuilder.h>
|
2023-03-12 20:21:57 +03:00
|
|
|
#include <AK/StringUtils.h>
|
2023-03-12 11:45:24 +03:00
|
|
|
#include <AK/StringView.h>
|
2020-02-06 17:04:03 +03:00
|
|
|
#include <LibCore/ArgsParser.h>
|
2023-04-05 18:05:41 +03:00
|
|
|
#include <LibCore/ConfigFile.h>
|
2020-03-11 00:41:01 +03:00
|
|
|
#include <LibCore/DateTime.h>
|
2022-01-17 23:06:59 +03:00
|
|
|
#include <LibCore/System.h>
|
2021-11-27 17:00:20 +03:00
|
|
|
#include <LibMain/Main.h>
|
2019-12-02 17:22:55 +03:00
|
|
|
#include <time.h>
|
|
|
|
|
2023-03-12 11:46:58 +03:00
|
|
|
#define ANSI_INVERT_OUTPUT "\e[7m"
|
|
|
|
#define ANSI_RESET_OUTPUT "\e[0m"
|
|
|
|
|
2023-03-04 02:51:06 +03:00
|
|
|
int constexpr month_width = "01 02 03 04 05 06 07"sv.length();
|
2023-03-12 11:52:42 +03:00
|
|
|
// three months plus padding between them
|
|
|
|
int constexpr year_width = 3 * month_width + 2 * " "sv.length();
|
2019-12-04 16:51:43 +03:00
|
|
|
|
|
|
|
int current_year;
|
|
|
|
int current_month;
|
2023-03-12 11:40:18 +03:00
|
|
|
int current_day;
|
2019-12-04 16:51:43 +03:00
|
|
|
|
2023-03-12 20:21:57 +03:00
|
|
|
static ErrorOr<int> weekday_index(StringView weekday_name)
|
|
|
|
{
|
|
|
|
auto is_same_weekday_name = [&weekday_name](StringView other) {
|
|
|
|
return AK::StringUtils::equals_ignoring_ascii_case(weekday_name, other);
|
|
|
|
};
|
|
|
|
|
|
|
|
if (auto it = AK::find_if(AK::long_day_names.begin(), AK::long_day_names.end(), is_same_weekday_name); !it.is_end())
|
|
|
|
return it.index();
|
|
|
|
if (auto it = AK::find_if(AK::short_day_names.begin(), AK::short_day_names.end(), is_same_weekday_name); !it.is_end())
|
|
|
|
return it.index();
|
|
|
|
if (auto it = AK::find_if(AK::mini_day_names.begin(), AK::mini_day_names.end(), is_same_weekday_name); !it.is_end())
|
|
|
|
return it.index();
|
|
|
|
|
|
|
|
if (auto numeric_weekday = AK::StringUtils::convert_to_int(weekday_name); numeric_weekday.has_value())
|
|
|
|
return numeric_weekday.value();
|
|
|
|
|
2023-04-27 20:51:19 +03:00
|
|
|
return Error::from_string_view("Unknown weekday name"sv);
|
2023-03-12 20:21:57 +03:00
|
|
|
}
|
|
|
|
|
2023-04-05 18:05:41 +03:00
|
|
|
static ErrorOr<int> default_weekday_start()
|
|
|
|
{
|
|
|
|
auto calendar_config = TRY(Core::ConfigFile::open_for_app("Calendar"sv));
|
2023-06-11 20:47:41 +03:00
|
|
|
String default_first_day_of_week = TRY(String::from_deprecated_string(calendar_config->read_entry("View"sv, "FirstDayOfWeek"sv, "Sunday"sv)));
|
2023-04-05 18:05:41 +03:00
|
|
|
return TRY(weekday_index(default_first_day_of_week));
|
|
|
|
}
|
|
|
|
|
2023-04-05 18:20:13 +03:00
|
|
|
static ErrorOr<StringView> month_name(int month)
|
|
|
|
{
|
|
|
|
int month_index = month - 1;
|
|
|
|
|
|
|
|
if (month_index < 0 || month_index >= static_cast<int>(AK::long_month_names.size()))
|
|
|
|
return Error::from_string_view("Month out of range"sv);
|
|
|
|
|
|
|
|
return AK::long_month_names.at(month_index);
|
|
|
|
}
|
|
|
|
|
2023-03-12 20:21:57 +03:00
|
|
|
static ErrorOr<String> weekday_names_header(int start_of_week)
|
|
|
|
{
|
|
|
|
// Generates a header in a style of "Su Mo Tu We Th Fr Sa"
|
|
|
|
|
|
|
|
Vector<String> weekdays;
|
|
|
|
for (size_t i = 0; i < AK::mini_day_names.size(); i++) {
|
|
|
|
size_t day_index = (i + start_of_week) % mini_day_names.size();
|
|
|
|
TRY(weekdays.try_append(TRY(String::from_utf8(AK::mini_day_names.at(day_index)))));
|
|
|
|
}
|
|
|
|
return TRY(String::join(' ', weekdays));
|
|
|
|
}
|
|
|
|
|
2023-04-04 18:42:52 +03:00
|
|
|
enum class Header {
|
|
|
|
MonthAndYear,
|
|
|
|
Month,
|
|
|
|
};
|
|
|
|
|
2023-03-12 20:21:57 +03:00
|
|
|
static ErrorOr<Vector<String>> month_lines_to_print(Header header_mode, int start_of_week, int month, int year)
|
2019-12-04 16:51:43 +03:00
|
|
|
{
|
2023-03-12 11:45:24 +03:00
|
|
|
Vector<String> lines;
|
2019-12-04 16:51:43 +03:00
|
|
|
|
|
|
|
// FIXME: Both the month name and month header text should be provided by a locale
|
2023-04-04 18:42:52 +03:00
|
|
|
String header;
|
|
|
|
switch (header_mode) {
|
|
|
|
case Header::Month:
|
|
|
|
header = TRY(String::from_utf8(TRY(month_name(month))));
|
|
|
|
break;
|
|
|
|
case Header::MonthAndYear:
|
|
|
|
header = TRY(String::formatted("{} - {}", TRY(month_name(month)), year));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
TRY(lines.try_append(TRY(String::formatted("{: ^{}s}", header, month_width))));
|
2023-03-12 20:21:57 +03:00
|
|
|
TRY(lines.try_append(TRY(weekday_names_header(start_of_week))));
|
2019-12-04 16:51:43 +03:00
|
|
|
|
2020-03-11 00:41:01 +03:00
|
|
|
auto date_time = Core::DateTime::create(year, month, 1);
|
|
|
|
int first_day_of_week_for_month = date_time.weekday();
|
2023-03-04 02:51:06 +03:00
|
|
|
int days_in_month = date_time.days_in_month();
|
2023-03-12 11:45:24 +03:00
|
|
|
|
2023-03-12 20:21:57 +03:00
|
|
|
first_day_of_week_for_month += 7 - start_of_week;
|
|
|
|
first_day_of_week_for_month %= 7;
|
|
|
|
|
2023-03-04 02:51:06 +03:00
|
|
|
Vector<String> days_in_row;
|
|
|
|
int day = 1;
|
|
|
|
for (int i = 1; day <= days_in_month; ++i) {
|
2019-12-04 16:51:43 +03:00
|
|
|
if (i - 1 < first_day_of_week_for_month) {
|
2023-03-04 02:51:06 +03:00
|
|
|
TRY(days_in_row.try_append(TRY(String::from_utf8(" "sv))));
|
2019-12-04 16:51:43 +03:00
|
|
|
} else {
|
2023-03-04 02:51:06 +03:00
|
|
|
if (year == current_year && month == current_month && day == current_day) {
|
|
|
|
TRY(days_in_row.try_append(TRY(String::formatted(ANSI_INVERT_OUTPUT "{:2}" ANSI_RESET_OUTPUT, day))));
|
2019-12-04 16:51:43 +03:00
|
|
|
} else {
|
2023-03-04 03:58:13 +03:00
|
|
|
TRY(days_in_row.try_append(TRY(String::formatted("{:2}", day))));
|
2019-12-04 16:51:43 +03:00
|
|
|
}
|
2023-03-04 02:51:06 +03:00
|
|
|
day++;
|
2019-12-04 16:51:43 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
if (i % 7 == 0) {
|
2023-03-04 02:51:06 +03:00
|
|
|
TRY(lines.try_append(TRY(String::join(' ', days_in_row))));
|
|
|
|
days_in_row.clear();
|
2019-12-04 16:51:43 +03:00
|
|
|
}
|
|
|
|
}
|
2023-03-12 11:45:24 +03:00
|
|
|
|
2023-03-04 02:51:06 +03:00
|
|
|
TRY(lines.try_append(TRY(String::join(' ', days_in_row))));
|
2023-03-12 11:45:24 +03:00
|
|
|
|
|
|
|
return lines;
|
2019-12-04 16:51:43 +03:00
|
|
|
}
|
|
|
|
|
2023-03-12 11:45:24 +03:00
|
|
|
static void print_months_side_by_side(Vector<String> const& left_month, Vector<String> const& center_month, Vector<String> const& right_month)
|
2019-12-04 16:51:43 +03:00
|
|
|
{
|
2023-03-12 11:45:24 +03:00
|
|
|
for (size_t i = 0; i < left_month.size() || i < center_month.size() || i < right_month.size(); i++) {
|
|
|
|
StringView left = i < left_month.size() ? left_month[i] : ""sv;
|
|
|
|
StringView center = i < center_month.size() ? center_month[i] : ""sv;
|
|
|
|
StringView right = i < right_month.size() ? right_month[i] : ""sv;
|
2019-12-04 16:51:43 +03:00
|
|
|
|
2023-03-12 11:52:42 +03:00
|
|
|
outln("{: <{}} {: <{}} {: <{}}", left, month_width, center, month_width, right, month_width);
|
2019-12-04 16:51:43 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-05 18:11:22 +03:00
|
|
|
static void go_to_next_month(int& month, int& year)
|
|
|
|
{
|
|
|
|
month += 1;
|
|
|
|
if (month > 12) {
|
|
|
|
year += 1;
|
|
|
|
month = 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void go_to_previous_month(int& month, int& year)
|
|
|
|
{
|
|
|
|
month -= 1;
|
|
|
|
if (month < 1) {
|
|
|
|
year -= 1;
|
|
|
|
month = 12;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-27 17:00:20 +03:00
|
|
|
ErrorOr<int> serenity_main(Main::Arguments arguments)
|
2019-12-02 17:22:55 +03:00
|
|
|
{
|
2023-04-05 18:05:41 +03:00
|
|
|
TRY(Core::System::pledge("stdio rpath cpath"));
|
2022-01-17 23:06:59 +03:00
|
|
|
|
2020-01-27 20:25:36 +03:00
|
|
|
int month = 0;
|
|
|
|
int year = 0;
|
2023-03-12 20:21:57 +03:00
|
|
|
StringView week_start_day_name {};
|
2023-04-05 18:11:22 +03:00
|
|
|
bool three_month_mode = false;
|
2023-03-04 16:03:05 +03:00
|
|
|
bool year_mode = false;
|
2019-12-04 16:51:43 +03:00
|
|
|
|
2020-02-02 14:34:39 +03:00
|
|
|
Core::ArgsParser args_parser;
|
2020-12-05 18:22:58 +03:00
|
|
|
args_parser.set_general_help("Display a nice overview of a month or year, defaulting to the current month.");
|
2023-02-23 18:53:40 +03:00
|
|
|
// FIXME: This should ensure one value gets parsed as just a year
|
2020-02-02 14:34:39 +03:00
|
|
|
args_parser.add_positional_argument(month, "Month", "month", Core::ArgsParser::Required::No);
|
|
|
|
args_parser.add_positional_argument(year, "Year", "year", Core::ArgsParser::Required::No);
|
2023-03-12 20:21:57 +03:00
|
|
|
args_parser.add_option(week_start_day_name, "Day that starts the week", "starting-day", 's', "day");
|
2023-03-04 16:03:05 +03:00
|
|
|
args_parser.add_option(year_mode, "Show the whole year at once", "year", 'y');
|
2023-04-05 18:11:22 +03:00
|
|
|
args_parser.add_option(three_month_mode, "Show the previous and next month beside the current one", "three-month-view", '3');
|
2021-11-27 17:00:20 +03:00
|
|
|
args_parser.parse(arguments);
|
2019-12-02 17:22:55 +03:00
|
|
|
|
2023-03-04 16:03:05 +03:00
|
|
|
if (three_month_mode && year_mode) {
|
|
|
|
warnln("cal: Cannot specify both --year and --three-month-mode at the same time");
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2019-12-02 17:22:55 +03:00
|
|
|
time_t now = time(nullptr);
|
|
|
|
auto* tm = localtime(&now);
|
2023-03-12 11:40:18 +03:00
|
|
|
current_year = tm->tm_year + 1900;
|
|
|
|
current_month = tm->tm_mon + 1;
|
|
|
|
current_day = tm->tm_mday;
|
2019-12-02 17:22:55 +03:00
|
|
|
|
2023-02-23 18:53:40 +03:00
|
|
|
// Hack: workaround one value parsing as a month
|
|
|
|
if (month && !year) {
|
2020-01-27 20:25:36 +03:00
|
|
|
year = month;
|
2023-02-23 18:53:40 +03:00
|
|
|
month = 0;
|
2019-12-02 17:22:55 +03:00
|
|
|
}
|
2019-12-04 16:51:43 +03:00
|
|
|
|
2023-03-04 16:03:05 +03:00
|
|
|
if (!month && year)
|
|
|
|
year_mode = true;
|
2020-01-27 20:25:36 +03:00
|
|
|
|
2023-04-05 18:05:41 +03:00
|
|
|
int week_start_day;
|
|
|
|
if (week_start_day_name.is_empty())
|
|
|
|
week_start_day = TRY(default_weekday_start());
|
|
|
|
else
|
2023-03-12 20:21:57 +03:00
|
|
|
week_start_day = TRY(weekday_index(week_start_day_name));
|
|
|
|
|
2020-01-27 20:25:36 +03:00
|
|
|
if (!year)
|
2023-03-12 11:40:18 +03:00
|
|
|
year = current_year;
|
2020-01-27 20:25:36 +03:00
|
|
|
if (!month)
|
2023-03-12 11:40:18 +03:00
|
|
|
month = current_month;
|
2020-01-27 20:25:36 +03:00
|
|
|
|
2019-12-04 16:51:43 +03:00
|
|
|
if (year_mode) {
|
2023-03-12 11:52:42 +03:00
|
|
|
outln("{: ^{}}", TRY(String::formatted("Year {}", year)), year_width);
|
2019-12-04 16:51:43 +03:00
|
|
|
|
2023-04-04 18:42:52 +03:00
|
|
|
for (int month_index = 1; month_index < 12; ++month_index) {
|
2023-03-12 11:45:24 +03:00
|
|
|
outln();
|
|
|
|
outln();
|
2023-03-12 20:21:57 +03:00
|
|
|
Vector<String> lines_left = TRY(month_lines_to_print(Header::Month, week_start_day, month_index++, year));
|
|
|
|
Vector<String> lines_center = TRY(month_lines_to_print(Header::Month, week_start_day, month_index++, year));
|
|
|
|
Vector<String> lines_right = TRY(month_lines_to_print(Header::Month, week_start_day, month_index, year));
|
2023-03-12 11:45:24 +03:00
|
|
|
print_months_side_by_side(lines_left, lines_center, lines_right);
|
2019-12-04 16:51:43 +03:00
|
|
|
}
|
2023-04-05 18:11:22 +03:00
|
|
|
} else if (three_month_mode) {
|
|
|
|
int month_on_left = month, year_on_left = year;
|
|
|
|
go_to_previous_month(month_on_left, year_on_left);
|
|
|
|
|
|
|
|
int month_on_right = month, year_on_right = year;
|
|
|
|
go_to_next_month(month_on_right, year_on_right);
|
|
|
|
|
|
|
|
Vector<String> lines_previous_month = TRY(month_lines_to_print(Header::MonthAndYear, week_start_day, month_on_left, year_on_left));
|
|
|
|
Vector<String> lines_current_month = TRY(month_lines_to_print(Header::MonthAndYear, week_start_day, month, year));
|
|
|
|
Vector<String> lines_next_month = TRY(month_lines_to_print(Header::MonthAndYear, week_start_day, month_on_right, year_on_right));
|
|
|
|
print_months_side_by_side(lines_previous_month, lines_current_month, lines_next_month);
|
2019-12-04 16:51:43 +03:00
|
|
|
} else {
|
2023-03-12 20:21:57 +03:00
|
|
|
Vector<String> lines = TRY(month_lines_to_print(Header::MonthAndYear, week_start_day, month, year));
|
2023-03-12 11:45:24 +03:00
|
|
|
for (String const& line : lines) {
|
|
|
|
outln("{}", line);
|
|
|
|
}
|
2019-12-04 16:51:43 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
2019-12-02 17:22:55 +03:00
|
|
|
}
|