Даты

Для работы с датами полезно определить структуру из следующих полей:

struct date
{
    int day, month, year;
};

Операции, которые полезно переопределить для дат: ==, !=, <, <=, >, >=, ++, --. Также будут полезны операции сложения даты и числа и разности двух дат.

Удобно также переводить даты в числа типа int, пронумеровавав все даты подряд. Для перевода даты в число желательно определить оператор int, для перевода числа в дату желательно определить конструктор от одного значения типа int.

Для вывода названия месяца по его номеру лучше всего использовать константный массив строк-названий:

const char * MONTH_NAMES[] = {"", "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"};
Для вывода названия дня недели лучше всего использовать константный массив строк-названий:
const char * DAY_OF_WEEK[] = {"Sunday", "Monday", "Tuesday", "Wednesday",  "Thursday", "Friday", "Saturday"};

Основным способом ввода-вывода дат должны быть переопределенные операторы << и >>, даты вводятся и выводятся в формате dd.mm.yyyy. Кроме этого будет ещё и текстовое представление даты вида January 1, 2017, для считывания которого необходимо реализовать метод from_text, а для вывода которого — метод to_text.

Упражнения для разминки

A: День года

По заданному числу n от 1 до 365 определите, на какое число какого месяца приходится день невисокосного года с номером n. Программа получает на вход целое число n и должна вывести два числа: число месяца (от 1 до 31) и номер месяца (от 1 до 12), на которое приходится данный день.

На вход программа получает одно число от 1 до 365. Программа должна вывести ответ в формате "Месяц день" (через один пробел), где "Месяц" - название месяца по-английски с заглавной буквы, "день" - число от 1 до 31.

Для определения номера дня в году лучше всего использовать константный массив количества дней в каждом месяце:

const int Months[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
Ввод Вывод
1
January 1
365
December 31

B: Обратная задача

Решите обратную предыдущей задачу: по записи названия месяца и дня определите номер дня в году.

Ввод Вывод
January 1
1
December 31
365

Упражнения на проектирование класса date

C: Перевод формата даты - 1

Дата задана в формате dd.mm.yyyy. Выведите ее в формате "Month d, y", где Month - английское название месяца, d - номер дня в месяце, без лидирующих нулей, y - номер года без лидирующих нулей.

Для вывода даты в строку удобно использовать вспомогательный объект класса ostringstream.

Требования к оформлению класса

Класс должен содержать оператор считывания даты из потока:

istream & operator>> (istream &, date &);

Класс должен содержать метод

string to_text() const;
возвращающую строку с данным представлением.
Ввод Вывод
12.10.2008
October 12, 2008
01.01.0001
January 1, 1

D: Перевод формата даты - 2

Решите обратную задачу.

Для считывания даты из строки удобно использовать вспомогательный объект класса istringstream.

Требования к оформлению класса

Класс должен содержать оператор вывода даты в поток:

ostream & operator<< (ostream &, const date &);

Класс должен содержать метод

void from_text(const string & s);
получающую на вход строку с описанием даты и устанавливающую дату на данное значение в соответствии со строкой.
Ввод Вывод
October 12, 2008
12.10.2008
January 1, 1
01.01.0001

E: Инкремент

Реализуйте операторы == и != для сравнения дат. Эти операторы должны возвращать значение типа bool.

Реализуйте оператор префиксного инкремента ++, который увеличивает дату на один день и возвращает ссылку на объект.

Префиксный оператор инкремента или декремента — это оператор, который пишется перед объектом (++a), он возвращает новое значение объекта.

Например, если оператор инкремента объявляется, как член класса, то объявление должно быть таким:

class date
{
    date & operator++()
    {
         //  Put your code here
         return *this;
    }
};
Ввод Вывод
12.10.2008
13.10.2008
31.12.2008
01.01.2009

F: Декремент

Реализуйте операторы <, <=, >, >= для сравнения дат. Эти операторы должны возвращать значение типа bool.

Реализуйте оператор постфиксного декремента --, который уменьшает дату на один день и возвращает ссылку на объект.

Постфиксный оператор инкремента или декремента — это оператор, который пишется после объекта (a--), он изменяет значение объекта, но возвращает ссылку на старое значение.

Например, если оператор декремента объявляется, как член класса, то объявление должно быть таким:

class date
{
    date operator--(int)
    {
         //  Put your code here
         return /* old saved value */;
    }
};

Параметр int, передаваемый оператору декремента, не используется. Он нужен для того, чтобы компилятор отличал префиксную и постфиксную форму записи оператора.

Также обратите внимание на то, что постфиксный инкремент должен возвращать не ссылку на объект, а сохраненное значение объекта, т.к. объект был изменен.

Ввод Вывод
13.10.2008
12.10.2008
01.01.2009
31.12.2008

G: Разность дат

Реализуйте оператор вычитания двух дат. Его можно оформить в виде метода:

class date
{
    int operator-(const date &) const;
};

или в виде функции

int operator-(const date &, const date &);

Оператор возвращает разность — на сколько дней различаются две даты.

При этом допускаются следующие ограничения: дата, соответствующая левому операнду не раньше даты правого операнда, вычитание может производиться при помощи операций инкремента или декремента.

В примере ниже из второй даты вычитается первая.

Ввод Вывод
01.01.0001
02.01.0001
1
29.02.2004
01.03.2005
366

H: День недели

По дате определите день недели, на который она приходится. Реализуйте алгоритм в виде метода

class date
{
    string day_of_the_week() const;
};

который возвращает строку, содержащую английское название дня недели с заглавной буквы.

Ввод Вывод
12.10.2008
Sunday
13.10.2008
Monday

I: День рождения

Задан день и месяц рождения в формате dd.mm. Задана текущая дата в формате dd.mm.yyyy. Определите, сколько дней осталось до дня рождения. Если сегодня день рождения, то необходимо вывести 0.

Ввод Вывод
19.04
19.04.2002
0
05.05
19.04.2002
16
29.02
28.02.2001
1096

На проверку нужно сдать программу целиком.

J: Номер дня по дате

Пронумеруем все даты подряд, считая, что 01.01.0001 имеет номер 1, 02.01.0001 — номер 2 и т.д. По заданной дате определите ее порядковый номер.

Решение оформите в виде оператора int.

class date
{
    operator int() const;
};

Обратите внимание, что при таком объявлении нельзя указывать тип возвращаемого значения, это должен быть int.

Пример использования: (int)date(1, 1, 2) возвращает 366.

Решение должно иметь сложность \(O(1)\), то есть не должно содержать циклы.

K: Разница дат - 2

Научитесь быстро вычислять разницу между двумя датами.

Необходимо реализовать оператор вычитания для дат, который не содержит циклов. Также левый операнд может быть меньше правого — в этом случае возвращается отрицательное число.

L: Дата по номеру дня

Решите обратную задачу — определите дату по номеру дня.

Решение оформите в виде конструктора

class date
{
    date(int);
};

Не забудьте про существование других конструкторов: от трёх целых чисел и конструктора по умолчанию (без аргументов).

В решении допускаются циклы, ограничение по времени — 1 секунда на 200.000 операций считывания числа, перевода числа в дату и вывода даты.

Несложные задачки с использованием дат

M: Самый старший

Имеется список людей с указанием их фамилии, имени и даты рождения. Напишите эффективную по времени работы и по используемой памяти программу, которая будет определять самого старшего человека из этого списка и выводить его фамилию, имя и дату рождения, а если имеется несколько самых старших людей с одинаковой датой рождения, то определять их количество.

На вход программе в первой строке подается количество людей в списке N. В каждой из последующих N строк находится информация в следующем формате:

<Фамилия> <Имя> <Дата рождения>

где <Фамилия> – строка, состоящая не более, чем из 20 символов без пробелов, <Имя> – строка, состоящая не более, чем из 20 символов без пробелов, <Дата рождения> – строка, имеющая вид DD.MM.YYYY.

Программа должна вывести дату рождения самого старшего человека в списке, затем через пробел его фамилию и имя. Если таких людей, несколько, то вместо фамилии и имени выводится их количество.

На проверку нужно сдать программу целиком.

Ввод Вывод
3
Ivan Petrov 01.05.1995
Petr Sergeev 29.04.1995
Sergey Ivanov 01.01.1996
29.04.1995 Petr Sergeev
3
Ivan Petrov 01.05.1995
Petr Sergeev 29.05.1995
Sergey Ivanov 01.05.1995
01.05.1995 2

N: Дни рождения

Имеется список сотрудников организации с указанием их фамилии, имени и даты рождения. Администрация ежедневно поздравляет всех сотрудников, родившихся в этот день. Напишите эффективную по времени работы и по используемой памяти программу, которая будет определять, в какой из дней года родилось больше всего сотрудников и выводить этот день (или несколько дней).

Формат входных данных аналогичен предыдущей задаче. Программа должна вывести список дат, в которые наибольшее число сотрудников отмечает дни рождения в формате DD.MM по возрастанию дат, каждая дата в отдельной строке.

На проверку нужно сдать программу целиком.

Ввод Вывод
5
Ivan Petrov 01.05.1995
Petr Sergeev 29.04.1995
Sergey Romanov 01.01.1996
Roman Grigoriev 01.01.1995
Grigoriy Ivanov 01.05.1995
01.01
01.05

O: Ближайшие дни рождения

Имеется список сотрудников организации с указанием их фамилии, имени и даты рождения. Напишите эффективную по времени работы и по используемой памяти программу, которая будет определять фамилию и имя самого молодого сотрудника, празднующего свой день рождения в течение ближайших семи дней от текущей даты (включая текущую дату).

На вход программе в первой сроке подается текущая дата, заданная в формате DD.MM.YYYY.

Во второй строке подается количество людей в списке N. В каждой из последующих N строк находится информация о каждом сотруднике, как в задаче T.

Известно, что у всех сотрудников даты рождения различаются. Программа должна вывести фамилию и имя самого молодого сотрудника, празднующего день рождения в ближайшие 7 дней или сообщение No birthdays in next week, если никто из сотрудников не празднует день рождения в ближайшие 7 дней.

На проверку нужно сдать программу целиком.

Ввод Вывод
25.11.2010
3
Ivan Petrov 01.12.1994
Petr Sergeev 25.11.1994
Sergey Romanov 02.12.1994
Ivan Petrov
25.11.2010
1
Sergey Romanov 02.12.1994
No birthdays in next week

Задачи на работу со временем

В этих задачах специальных требований к решениям нет, на проверку сдаётся программа целиком. Но для хранения времени удобно создать:

class Time
{
    int hours;
    int minutes;
    int seconds;
};

Использовать для названия структуры идентификатор time нельзя, так как такое имя уже используется в стандартной библиотеке языка C.

Времена будут, как правило, задаваться в формате hh:mm:ss, где hh принимает значения от 00 до 23, mm и ss - значения от 00 до 59 или в формате hh:mm.

P: Таймер

Часы показывают время в формате hh:mm:ss. На этих часах запустили таймер, который прозвенит через n секунд. Определите время, которое будет на часах, когда прозвенит таймер. n может принимать значения от 0 до 109. Решение задачи не дожно содержать циклов. Постарайтесь также не использовать условную инструкцию.

Пример

Ввод Вывод
09:00:00
90
09:01:30
23:59:59
1
00:00:00

Q: Разница времен

Профессор лег спать, когда на часах было время h1:m1:s1, а когда он проснулся было время h2:m2:s2. Определите, сколько времени спал профессор, если известно, что он проспал не более суток. Время выведите в формате hh:mm:ss.
Решение задачи не должно содержать циклы. Постарайтесь также не использовать условную инструкцию.

Пример

Ввод Вывод
14:00:00
21:10:30
07:10:30
22:00:00
07:00:00
09:00:00

R: Будильники

Будильник в сотовом телефоне можно настроить так, чтобы он звонил каждый день в одно и то же время, либо в указанное время в определенный день недели. Независимо можно настроить несколько будильников.

По информации о будильниках и текущему времени и дню недели определите, когда прозвонит очередной будильник.

В первой строке вводится текущий день недели (число от 1 до 7), затем через пробел, текущее время в формате HH:MM.

Во второй строке вводится одно натуральное число N, не превосходящее 100 – количество будильников.

В следующих N строках вводится описание N будильников в таком же формате. Значение дня недели, равное 0, означает, что будильник звонит каждый день.

Выведите номер дня недели и время, когда будильник зазвонит в следующий раз, в таком же формате.

Ввод Вывод
2 10:20
2
1 23:15
0 10:10
3 10:10
7 01:01
3
7 00:59
7 23:59
7 01:01
7 01:01

S: Таймер-2

Таймер - это часы, которые умеют подавать звуковой сигнал по прошествии некоторого периода времени. Напишите программу, которая определяет, когда должен быть подан звуковой сигнал.

Первая строка входных данных содержит текущее время в формате HH:MM:SS (с ведущими нулями). При этом оно удовлетворяет ограничениям: HH — от 00 до 23, MM и SS - от 00 до 59.

Вторая строка входных данных содержит интервал времени, который должен быть измерен. Интервал записывается в формате H:M:S (где H, M и S — от 0 до 109, без ведущих нулей). При этом если H=0 (или H=0 и M=0), то они могут быть опущены. Например, 100:60 может быть записано и как 101:0, 1:41:0, 0:100:60, 0:101:0 и т.д. Запись 42 — это то же самое, что 0:0:42, а 100:100:100 то же самое, что 101:41:40.

Выведите в формате HH:MM:SS время, во сколько прозвучит звуковой сигнал. При этом если сигнал прозвучит не в текущие сутки, то дальше должна следовать запись +<кол во> days. Например, если сигнал прозвучит на следующий день – то +1 days.

Ввод Вывод
01:01:01
48:0:0
01:01:01+2 days
01:01:01
58:119
02:01:00
23:59:59
1
00:00:00+1 days

Unix time

В следующих трех задачах вам нужно разобраться со стандартными функциями языка C, работающими со временем, а не реализовывать их самостоятельно. Эти функции описаны в заголовочном файле time.h.

В операционной системе UNIX, и во всех системах, имеющих сходную архитектуру (удовлетворяющих стандарту POSIX), например, Linux, Android, macOS, iOS, время хранится, как целое число, равное количеству секунд, прошедших с полуночи 1 января 1970 года, то есть число 0 соответствует времени 0:00:00 01.01.1970. Секунды координации не учитываются.

Увидеть текущее время в формате UNIX (называется также Unix timestamp) и перевести время из формата UNIX в человекочитаемый формат можно на сайте unixtimestamp.com.

Для хранения времени используется 32-битное знаковое целое число, что приведёт к концу света в 03:14:07 19 января 2038 UTC. Поэтому в 64-битных системах UNIX time хранится в 64-битной переменной. Соответствующий тип данных в языке C называется time_t, он является 32-битным или 64-битным знаковым целым числом.

Структура данных tm хранит дату и время в виде нескольких полей: год, месяц, день, часы, минуты, секунды, день недели, количество дней с начала года, флаг использования летнего времени. Эта структура также используется многими функциями, работающих с временем.

T: UNIX timestamp

Напечатайте на экран текущее значение UNIX timestamp (в примере вывод соответствует вызову программы в 0:00:00 1 января 2017 UTC).

Используйте функцию time.

Ввод Вывод
 
1483228800

U: UNIX timestamp в обычное время

Переведите значение из формата UNIX timestamp в обычную дату и время. Выведите результат в формате dd.mm.yyyy hh:mm:ss.

Разберитесь с функциями gmtime и strftime.

Обратите внимание, что time_t в тестирующей системе является 64-битным типом данных, а у вас на компьютере он может быть 32-битным.

Ввод Вывод
1483228800
01.01.2017 00:00:00

V: Обычное время в UNIX timestamp

Решите обратную задачу. Вам поможет функция mktime. Обратите внимание на то, что функия mktime работает с местным временем, а вам нужно время в UTC. Обратите внимание на вызов функции setenv в примере на cppreference.com. Подробней о переменной TZ в GNU C Library.

Ввод Вывод
01.01.2017 00:00:00
1483228800

Задачки посложнее

W: Кассы

На одном из московских вокзалов билеты продают N касс. Каждая касса работает без перерыва определенный промежуток времени по фиксированному расписанию (одному и тому же каждый день). Требуется определить, на протяжении какого времени в течение суток работают все кассы одновременно.

Программа получает на вход целое число N (0<N≤1000).

В каждой из следующих N строк записано время начало и время окончания работы кассы в формате HH:MM через пробел.

Время открытия означает, что в соответствующую ему минуту касса уже работает, а время закрытия что в соответствующую минуту касса уже не работает. Например, касса, открытая с 10:30 18:30 ежесуточно работает 480 минут.

Если время открытия совпадает с временем закрытия, то касса работает круглосуточно. Если первое время больше второго, то касса начинает работу до полуночи, а заканчивает на следующий день.

Требуется вывести одно число суммарное время за сутки (в минутах), на протяжении которого работают все N касс.

Ввод Вывод
3
01:00 23:00
12:00 12:00
22:00 02:00
120
2
09:30 14:00
14:15 21:00
0
2
14:00 18:00
10:00 14:01
1

X: Пятница, 13-е

Докажите, что 13-е число месяца чаще всего приходится на пятницу.
Напишите программу, которая выводит на экран 7 чисел: вероятности выпадения 13 числа каждого месяца на понедельник, вторник, среду, четверг, пятницу, субботу, воскресенье. Например, если бы данные вероятности были бы равны, то программа должна вывести следущий текст:

0.142857
0.142857
0.142857
0.142857
0.142857
0.142857
0.142857

Y: Игра с датами

Играет два человека. Задается какая-то дата високосного года. Каждый игрок на своем ходу называет более позднюю дату, увеличивая на 1 или на 2 либо день в месяце, либо месяц, но не то и другое сразу. При этом результат должен оставаться корректной датой. Игрок, назвавший 31 декабря, проигрывает. Кто выигрывает при правильной игре: первый или второй.

Программа получает на вход дату в формате DD.MM и должна вывести одно число: номер выигрывающего игрока.

Ввод Вывод
30.12
2
29.12
1
29.11
2

Z: Выходные дни

Для каждого данного года посчитайте количество выходных дней в этом году (то есть количество суббот и воскресений).

Программа получает на вход число \(N\) (\(1\le N\le 10^6\)). В каждой из следующих \(N\) строк записано по одному числу от 2000 до \(10^9\) — номера годов.

Для каждого номера года выведите количество выходных дней в соответствующем годе.

Ввод Вывод
2
2013
2014
104
104

Если вам мало...

ZZ: Календарь на год

Напишите программу, которая выводит на экран календарь на год примерно в таком виде, как это делает программа cal.

Календарь состоит из блоков, каждый из которых соответствует одному месяцу. Блоки расположены в виде таблицы из k столбцов и 12/k строк (k выбирается делителем числа 12). Месяцы выводятся в следующем порядке: первая строка содержит блоки, соответствующие месяцам с первого по k-ый, следующая – с (k + 1)-го по 2k-ый, и т. д.

Ширина всех блоков в столбце должна быть одинакова, высота всех блоков равна семи (числу дней в неделе). Между блоками различных строк таблицы выводится пустая строка, в каждой строке между соседними блоками из разных столбцов выводится три пробела.

Блок, соответствующий месяцу, устроен следующим образом. Каждой (в том числе неполной) неделе данного месяца в блоке соответствует столбец, имеющий ширину, равную двум. Между двумя соседними столбцами в каждой строке выводится один пробел. Если несколько блоков располагаются в одном столбце календаря, то для выравнивания ширины в те блоки, которые содержат меньше недель, в конец добавляется необходимое число пустых столбцов-недель. При этом разные столбцы календаря могут иметь разную ширину.

Все числа месяца заносятся в блок, соответствующий этому месяцу. Число заносится в строку блока, соответствующую дню недели, на который приходится число в этом месяце. Число заносится в столбец блока, соответствующий неделе, к которой относится данное число. Однозначные числа дополняются слева одним пробелом. Таким образом, числа в столбце оказываются выравнены по правому краю.

 

Программа получает на вход описание года, календарь для которого следует вывести. Оно содержит три числа: \(d\) – день недели, на который приходится первое января (\(1\le d \le 7\)), \(l\); – является ли год високосным (\(l=1\) означает, что год является високосными, \(l=0\) – что не является) и \(k\) – количество столбцов в календаре (\(k\) – одно из чисел 1, 2, 3, 4, 6, 12).

Выведите календарь, отформатированный в соответствии с условием задачи. Проверяющая программа в этой задаче игнорирует пробелы в конце строк. В остальном календарь должен быть отформатирован в точности так, как описано в условии.

Ввод Вывод
4 1 4
    5 12 19 26          2  9 16 23    1  8 15 22 29       5 12 19 26   
    6 13 20 27          3 10 17 24    2  9 16 23 30       6 13 20 27   
    7 14 21 28          4 11 18 25    3 10 17 24 31       7 14 21 28   
 1  8 15 22 29          5 12 19 26    4 11 18 25       1  8 15 22 29   
 2  9 16 23 30          6 13 20 27    5 12 19 26       2  9 16 23 30   
 3 10 17 24 31          7 14 21 28    6 13 20 27       3 10 17 24      
 4 11 18 25          1  8 15 22 29    7 14 21 28       4 11 18 25      
                                                                       
    3 10 17 24 31       7 14 21 28       5 12 19 26       2  9 16 23 30
    4 11 18 25       1  8 15 22 29       6 13 20 27       3 10 17 24 31
    5 12 19 26       2  9 16 23 30       7 14 21 28       4 11 18 25   
    6 13 20 27       3 10 17 24       1  8 15 22 29       5 12 19 26   
    7 14 21 28       4 11 18 25       2  9 16 23 30       6 13 20 27   
 1  8 15 22 29       5 12 19 26       3 10 17 24 31       7 14 21 28   
 2  9 16 23 30       6 13 20 27       4 11 18 25       1  8 15 22 29   
                                                                       
    6 13 20 27          4 11 18 25    1  8 15 22 29       6 13 20 27   
    7 14 21 28          5 12 19 26    2  9 16 23 30       7 14 21 28   
 1  8 15 22 29          6 13 20 27    3 10 17 24       1  8 15 22 29   
 2  9 16 23 30          7 14 21 28    4 11 18 25       2  9 16 23 30   
 3 10 17 24          1  8 15 22 29    5 12 19 26       3 10 17 24 31   
 4 11 18 25          2  9 16 23 30    6 13 20 27       4 11 18 25      
 5 12 19 26          3 10 17 24 31    7 14 21 28       5 12 19 26