Laboratory 6

Laboratory 6 – Date Structures and Calendar Calculations

This laboratory extends the work with struct, header files, and multi-file programs by solving a real problem: representing calendar dates and calculating the number of days between two dates with correct handling of leap years and the Julian–Gregorian calendar transition.

struct DAY days.h days.c gcc -c calendar algorithms

Laboratory Overview

What is new in this laboratory.

In the previous laboratory, you used struct POINT and split code into separate files. In this laboratory, you will apply the same ideas to a more realistic problem: a structure representing a date and a function that returns the number of days between two dates.

This laboratory builds directly on Laboratory 5: structures are no longer only small geometric containers. They now describe a real-world object with non-trivial rules.
Main idea of this lab:
structure β†’ validation β†’ day counting β†’ separate compilation β†’ testing

The main challenge is not only writing a function, but also thinking carefully about calendar rules, leap years, date ordering, and the historical switch from the Julian calendar to the Gregorian calendar.

Learning Outcomes

  • work in the correct laboratory directory on the AGH UNIX server,
  • define and use a structure representing a date,
  • write declarations in a custom header file and definitions in a source file,
  • use gcc -c for separate compilation,
  • implement a function that returns the number of days between two dates,
  • handle leap years correctly,
  • take into account the Julian–Gregorian calendar transition in 1582,
  • write a small test program for a multi-file solution,
  • write readable and properly indented code without global variables.

Task 0 β€” Reminder and preparation

Before starting the exercises, log in to the AGH server and prepare the working directory.

Step 1 β€” Log in to the AGH UNIX server

ssh your_login@student.agh.edu.pl

Step 2 β€” Go to the course directory

cd ~/I2PL

Step 3 β€” Create the directory for Laboratory 6

mkdir lab6
cd lab6

Step 4 β€” General rules

  • Work only in ~/I2PL/lab6.
  • Keep all files for this exercise in this directory.
  • Do not use global variables.
  • Keep the code readable and properly indented.
  • Compile with warnings enabled.
gcc -Wall program.c -o program
This laboratory continues the same workflow as before: work on student.agh.edu.pl, use nano, compile with -Wall, and run programs with ./program.

Task 1 β€” Define a structure for dates

Create a structure representing a calendar date.

Step 1 β€” Create the header file

nano days.h

Step 2 β€” Add include guards

#ifndef DAYS_H
#define DAYS_H

/* content */

#endif

Step 3 β€” Define the structure

Define a structure named DAY that stores one calendar date. A practical version is:

typedef struct {
    int day;
    int month;
    int year;
} DAY;

Step 4 β€” Add the function declaration

int days(DAY a, DAY b);
How struct, typedef, and initialization work in C

In C, structures can be introduced in several different ways. The form used in this laboratory is based on typedef, which creates a type alias.

1. Structure type with a name

struct DAY {
    int day;
    int month;
    int year;
};
struct DAY d1, d2;

Here, struct DAY is the type name. When using this form, the keyword struct is part of the type name.

2. Structure definition with variables

struct DAY {
    int day;
    int month;
    int year;
} d1, d2;

This defines the structure type and declares variables of this type at the same time.

3. Anonymous structure with variables

struct {
    int day;
    int month;
    int year;
} d1, d2;

This creates variables, but the structure type has no name. You cannot later declare another variable using this anonymous structure type.

4. typedef β€” creating an alias

typedef struct {
    int day;
    int month;
    int year;
} DAY;
DAY d1, d2;

The keyword typedef creates an alias. In this example, DAY becomes a shorter name for this structure type.

In this laboratory, we use DAY instead of struct DAY.

5. typedef with a named structure

typedef struct DAY {
    int day;
    int month;
    int year;
} DAY;

This defines both a structure tag (struct DAY) and a type alias (DAY). Both names refer to the same structure type.

6. Initialization of structure variables

You can initialize structure variables in several ways.

Direct initialization

DAY d1 = {1, 1, 2000};

Values are assigned in the order of the fields in the structure definition: day, month, year.

Designated initializers

DAY d2 = {
    .day = 31,
    .month = 12,
    .year = 1999
};
Designated initializers are often clearer because the field names are visible.

Partial initialization

DAY d3 = {15, 5};

Missing fields are initialized to zero. In this example, year becomes 0.

Assignment after declaration

DAY d4;

d4.day = 10;
d4.month = 6;
d4.year = 2024;

Copying structures

DAY a = {1, 1, 2000};
DAY b = a;   /* copy all fields */
The order of fields matters when using positional initialization. If you change the order of fields in the structure definition, you must update such initializers.

Task 2 β€” Create the implementation file

Move the function definition into a separate source file.

Step 1 β€” Create the file

nano days.c

Step 2 β€” Include your header

#include "days.h"

Step 3 β€” Implement the function

Define the function days in days.c. Its job is to return the number of days between two dates.

Step 4 β€” Keep helper functions private to this file

If you need helper functions such as date validation, leap-year checking, or conversion of a date into an absolute day number, place them in days.c.

Small internal helper functions are a good use case for static.

Minimal working version β€” stub implementation

Before implementing the full algorithm, first make sure the program compiles and links correctly.

This approach lets you test the structure of the program before writing the actual calendar logic.

Step 1 β€” Temporary implementation in days.c

#include "days.h"

int days(DAY a, DAY b) {
    return -1;   /* temporary placeholder */
}
This is a stub: a temporary implementation used to test compilation and linking.

Step 2 β€” Minimal ex19.c

#include <stdio.h>
#include "days.h"

int main(void) {
    DAY d1 = {1, 1, 2000};
    DAY d2 = {2, 1, 2000};

    printf("days = %d\n", days(d1, d2));

    return 0;
}

Step 3 β€” Compile and run

gcc -Wall -c days.c
gcc -Wall -c ex19.c
gcc ex19.o days.o -o ex19
./ex19
If the program prints -1, the build process is correct. You can now replace the stub with the real implementation.
Do not try to implement everything at once. First make sure the project compiles and links correctly.

Task 3 β€” Write a test program

Create a short program that uses the module and checks whether it works correctly.

You may start from the minimal version shown earlier and extend it with more test cases.

Step 1 β€” Create the file

nano ex19.c

Step 2 β€” Include the header

#include <stdio.h>
#include "days.h"

Step 3 β€” Create sample dates

In main, define a few test dates as variables of type DAY and print the results of calling days.

Step 4 β€” Test more than one case

Check at least:

  • two dates in the same year,
  • a case involving a leap year,
  • a case where the earlier and later date are entered in reversed order,
  • a case around the year 1582.

Task 4 β€” Use separate compilation

Compile source files separately and link the final program.

Step 1 β€” Recommended workflow

Recompile days.c only when you change the implementation of the module. Recompile ex19.c when you change the test program.

gcc -Wall -c days.c      # recompile only if days.c changed
gcc -Wall -c ex19.c
gcc ex19.o days.o -o ex19
./ex19
If only ex19.c changes, you do not need to recompile days.c. This is the main advantage of separate compilation.
Object files (.o) are reused unless their source file changes.

Step 2 β€” Simplified linking with an already compiled module

If days.o already exists and is up to date, you may compile and link the test program in one command:

gcc -Wall ex19.c days.o -o ex19
./ex19

Step 3 β€” What -c means

The option -c tells gcc to compile a source file into an object file, but not to link the final executable yet.

days.c  β†’  days.o
ex19.c  β†’  ex19.o
days.o + ex19.o  β†’  ex19

Task 5 β€” Handle leap years correctly

Make sure the result is correct for both ordinary and leap years.

Step 1 β€” Distinguish Julian and Gregorian rules

In the Julian calendar, a leap year occurs every year divisible by 4. In the Gregorian calendar, the rule is more precise:

divisible by 4      β†’ leap year
except divisible by 100
unless divisible by 400

Step 2 β€” Apply the correct rule depending on the date

Since this exercise explicitly mentions the calendar change in 1582, your program should not blindly use only one leap-year rule for all centuries.

A solution that uses the modern Gregorian rule for all historical dates is incomplete for this exercise.

Task 6 β€” Take into account the calendar change in 1582

Handle the transition from the Julian calendar to the Gregorian calendar.

The exercise notes that the Julian calendar was superseded by the Gregorian calendar in 1582. In practice, this means that some dates were skipped during the reform.

For a simplified historical model used in programming exercises, it is common to assume that after 04.10.1582 the next date is 15.10.1582.
For this exercise, use the simplified model: after 04.10.1582 comes 15.10.1582. Dates from 05.10.1582 to 14.10.1582 do not exist.

Step 1 β€” Decide how to represent the missing days

A sensible approach is to convert each date into an absolute day count and make sure that the skipped dates in October 1582 are not counted as existing calendar dates.

Step 2 β€” Reject or avoid invalid dates from the missing interval

Dates from 05.10.1582 to 14.10.1582 should not be treated as normal valid dates in this model.

The sample result
01.01.1582 – 31.12.1582  β†’  354 days
shows that the missing days must affect the calculation.

Task 7 β€” Make the function independent of input order

The result must be correct no matter which date is earlier.

Step 1 β€” Compare the two dates

Before counting, determine which date is earlier and which is later.

Step 2 β€” Return a non-negative difference

A convenient interpretation for this exercise is that the function returns the absolute number of days between the two dates.

This means that swapping the arguments should not change the result.

Definition used in this laboratory

Clarify what the function should return.

The function days returns the absolute number of days between two dates.

Examples:

01.01.2024 – 02.01.2024  β†’  1 day
02.01.2024 – 01.01.2024  β†’  1 day
01.01.2024 – 01.01.2024  β†’  0 days
  • from one day to the next day the result is 1,
  • the result is always non-negative,
  • the order of arguments does not change the result.
Do not interpret this as the number of full days strictly inside the interval. There are 0 days between today and tomorrow in that sense, but this function returns 1.

Suggested Test Cases

Sample cases inspired by the original exercise.

01.01.2024 – 01.01.2024  β†’      0 days
01.01.2024 – 02.01.2024  β†’      1 day
02.01.2024 – 01.01.2024  β†’      1 day
12.12.1234 – 20.01.1410  β†’  63958 days
12.12.1234 – 31.10.1972  β†’ 269500 days
31.10.1972 – 20.01.1410  β†’ 205542 days
01.01.1582 – 31.12.1582  β†’    354 days
Use these cases to test your implementation, but also add your own shorter and simpler examples.

Implementation Strategy

There are several correct ways to solve the problem. A clean and reliable approach is:

  1. validate the input date,
  2. determine whether the date belongs to the Julian or Gregorian part of the calendar,
  3. convert the date to an absolute day number,
  4. subtract the two day numbers and take the absolute value.
This is usually easier and less error-prone than counting days one month or one year at a time.
The function should handle invalid dates appropriately (for example, by returning -1).

Mini Theory β€” Why gcc -c matters

In this laboratory, the -c option is important because the program is split into separate files.

gcc -Wall -c days.c   β†’ days.o
gcc -Wall -c ex19.c   β†’ ex19.o
gcc ex19.o days.o -o ex19

Separate compilation allows each source file to be compiled independently. Only after that does the linker combine the object files into one executable program.

Checklist before submission

  • defining and using a structure representing a date,
  • putting declarations in days.h and definitions in days.c,
  • using gcc -c correctly for separate compilation,
  • correct handling of leap years,
  • taking into account the calendar transition in 1582,
  • obtaining the same result regardless of argument order,
  • writing readable and properly indented code.