Fun with Dates

teekay

TK

Posted on April 15, 2020

Fun with Dates

Two girls sitting together and eating icecream in restaurant


This post was first published at TK's blog.

As an ex-Rubyist, I always liked to work with Ruby dates (mental note: not the timezone part). I liked the human way on how Ruby and Rails provide methods to handle the Date object.

In Ruby, we can get the current date by doing:

require 'date'

Date.today # #<Date: 2020-04-05 ((2458945j,0s,0n),+0s,2299161j)>
Enter fullscreen mode Exit fullscreen mode

This is pretty cool! I can send a simple message to the Date object "hey, provide me the today date" by calling the today method.

Or simply get the year, month, day.

date = Date.today
date.year # 2020
date.month # 4
date.day # 5
Enter fullscreen mode Exit fullscreen mode

Using Rails, it is also possible to call the yesterday method.

Date.yesterday
Enter fullscreen mode Exit fullscreen mode

Rails also provides other interesting APIs: beginning_of_month, minutes.ago, days.ago.

So after a long time with Ruby and Rails, I started using JavaScript more and more. But the JavaScript Date object was really strange for me. I wanted to use all the Ruby/Rails date APIs but in JavaScript and Typescript.

I didn't want to monkey patch or build new methods in the JavaScript Date object. I could just provide some simple functions and handle the Date internally.

Dating dates

First things first: I wanted to better understand the Date object. How do we create it?

new Date();
Enter fullscreen mode Exit fullscreen mode

By simply instantiate the Date object. We get the representation of now (the current date).

The other APIs I need to try was: getDate, getMonth, and getFullYear. These are all methods to handle the date.

const day: number = now.getDate(); // 5
const month: number = now.getMonth(); // 3
const year: number = now.getFullYear(); // 2020
Enter fullscreen mode Exit fullscreen mode

We could experiment with a whole bunch of other methods here, but I think we are good to move to the next part.

Fun with dates

In this part, we will build functions! I wanted to try creating this API:

  • day
  • month
  • year
  • today
  • yesterday
  • beginningOfDay
  • beginningOfMonth
  • beginningOfYear
  • get(1).dayAgo
  • get(2).daysAgo
  • get(1).monthAgo
  • get(2).monthsAgo
  • get(1).yearAgo
  • get(2).yearsAgo

day, month, and year

In this case, we provide a date and it will return the day of this date we provided.

const day = (date: Date): number => date.getDate();
const month = (date: Date): number => date.getMonth();
const year = (date: Date): number => date.getFullYear();
Enter fullscreen mode Exit fullscreen mode

And we can use it like:

const now = new Date();

day(now); // 5
month(now); // 3
year(now); // 2020
Enter fullscreen mode Exit fullscreen mode

today and yesterday

With today function, we could just return the new Date() and we are good. But this returns the representation of now with "time" included.

new Date(); // 2020-04-05T18:58:45
Enter fullscreen mode Exit fullscreen mode

But it would be great to return the beginning of the day. We could simply pass the day, month, and year to the Date and it will generate this for us.

const today = (): Date => {
  const now: Date = new Date();
  const day: number = now.getDate();
  const month: number = now.getMonth();
  const year: number = now.getFullYear();

  return new Date(year, month, day);
};
Enter fullscreen mode Exit fullscreen mode

Great. The yesterday function would work very similarly. Just subtract the day and we are good to go.

const yesterday = (): Date => {
  const now: Date = new Date();
  const day: number = now.getDate();
  const month: number = now.getMonth();
  const year: number = now.getFullYear();

  return new Date(year, month, day - 1);
};
Enter fullscreen mode Exit fullscreen mode

But what happens when we subtract the day if the day is the first day of the month?

// date to handle
new Date(2020, 3, 1); // 2020-04-01

// when subtracting the day: from 1 to 0
new Date(2020, 3, 0); // 2020-03-31
Enter fullscreen mode Exit fullscreen mode

And what happens if it is the first day of the year?

// date to handle
new Date(2020, 0, 1); // 2020-01-01

// when subtracting the day: from 1 to 0
new Date(2020, 0, 0); // 2019-12-31
Enter fullscreen mode Exit fullscreen mode

Yes, JavaScript can be pretty smart too!

With these two new functions, we can also refactor the logic to get the separated date into a separate function.

const getSeparatedDate = (): { day: number, month: number, year: number } => {
  const now: Date = new Date();
  const day: number = now.getDate();
  const month: number = now.getMonth();
  const year: number = now.getFullYear();

  return { day, month, year };
};
Enter fullscreen mode Exit fullscreen mode

Let's improve this! This returned type could be a Typescript type.

type SeparatedDate = {
  day: number
  month: number
  year: number
};
Enter fullscreen mode Exit fullscreen mode

Less verbose now:

const getSeparatedDate = (): SeparatedDate => {
  const now: Date = new Date();
  const day: number = now.getDate();
  const month: number = now.getMonth();
  const year: number = now.getFullYear();

  return { day, month, year };
};
Enter fullscreen mode Exit fullscreen mode

In this case, we are always returning the day, month, and year attributes of the current date. But what if we want to pass a different date? A new argument to the rescue:

const getSeparatedDate = (now: Date = new Date()): SeparatedDate => {
  const day: number = now.getDate();
  const month: number = now.getMonth();
  const year: number = now.getFullYear();

  return { day, month, year };
};
Enter fullscreen mode Exit fullscreen mode

Now we have a function that can receive a new date, but if it doesn't, it just uses the default value: the representation of now.

How does our functions today and yesterday look like now?

const today = (): Date => {
  const { day, month, year }: SeparatedDate = getSeparatedDate();

  return new Date(year, month, day);
};

const yesterday = (): Date => {
  const { day, month, year }: SeparatedDate = getSeparatedDate();

  return new Date(year, month, day - 1);
};
Enter fullscreen mode Exit fullscreen mode

Both functions use the getSeparatedDate function to get the Date attributes and return the appropriate date.

The beginning of everything

To build the beginningOfDay, it would look exactly of the today function, as we want to the current date but at the beginning of the day.

const beginningOfDay = (date: Date = new Date()): Date => {
  const { day, month, year }: SeparatedDate = getSeparatedDate();

  return new Date(year, month, day);
};
Enter fullscreen mode Exit fullscreen mode

Nothing special here.

But just minor comment if you didn't notice: At first, I'm built this function to get the beginning of the day of the current day. But I wanted to make it flexible enough to get the beginning of the day of other days too.

So "argument", right? Now the function receives a date, but it is flexible to not receive it too. I just handle it with a default value of the current date.

For the beginningOfMonth, it will look pretty much the same, but instead of using the day, we just set it to 1.

const beginningOfMonth = (date: Date = new Date()): Date => {
  const { month, year }: SeparatedDate = getSeparatedDate();

  return new Date(year, month, 1);
};
Enter fullscreen mode Exit fullscreen mode

You got it, the beginningOfYear is similar. But also changing the month attribute.

const beginningOfYear = (date: Date = new Date()): Date => {
  const { year }: SeparatedDate = getSeparatedDate();

  return new Date(year, 0, 1);
};
Enter fullscreen mode Exit fullscreen mode

Traveling back in time

Now the get(1).dayAgo API. We could build a get function that receives a number and return an object like:

{
  dayAgo,
  monthAgo,
  yearAgo
}
Enter fullscreen mode Exit fullscreen mode

For each attribute of this object, it would be the returned value we expect.

const get = (n: number): { dayAgo: Date, monthAgo: Date, yearAgo: Date } => {
  const { day, month, year }: SeparatedDate = getSeparatedDate();

  const dayAgo: Date = new Date(year, month, day - n);
  const monthAgo: Date = new Date(year, month - n, day);
  const yearAgo: Date = new Date(year - n, month, day);

  return { dayAgo, monthAgo, yearAgo };
};
Enter fullscreen mode Exit fullscreen mode

What about a DateAgo type?

type DateAgo = {
  dayAgo: Date
  monthAgo: Date
  yearAgo: Date
};
Enter fullscreen mode Exit fullscreen mode

And now using the new type:

const get = (n: number): DateAgo => {
  const { day, month, year }: SeparatedDate = getSeparatedDate();

  const dayAgo: Date = new Date(year, month, day - n);
  const monthAgo: Date = new Date(year, month - n, day);
  const yearAgo: Date = new Date(year - n, month, day);

  return { dayAgo, monthAgo, yearAgo };
};
Enter fullscreen mode Exit fullscreen mode

We build each attribute: dayAgo, monthAgo, and yearAgo by basically handling the Date object as we know.

But now we also need to implement the object in the plural: daysAgo, monthsAgo, and yearsAgo. But only for a number greater than 1.

For these new attributes, we don't need to create a whole new date again. We can use the same value from the singular attributes.

We also need to handle the number received.

  • if it is greater than 1: return the object with plural attributes
  • otherwise: return the object with singular attributes
const get = (n: number): DateAgo | DatesAgo => {
  const { day, month, year }: SeparatedDate = getSeparatedDate();

  const dayAgo: Date = new Date(year, month, day - n);
  const monthAgo: Date = new Date(year, month - n, day);
  const yearAgo: Date = new Date(year - n, month, day);

  const daysAgo: Date = dayAgo;
  const monthsAgo: Date = monthAgo;
  const yearsAgo: Date = yearAgo;

  return n > 1
    ? { daysAgo, monthsAgo, yearsAgo }
    : { dayAgo, monthAgo, yearAgo };
};
Enter fullscreen mode Exit fullscreen mode
  • In this case, I also created the DatesAgo type and used the Typescript Union Type feature.
  • We reuse the singular values.
  • And do a simple ternary to handle the number received.

But what if we pass a 0 or negative value? We can throw an error:

const get = (n: number): DateAgo | DatesAgo => {
  if (n < 1) {
    throw new Error('Number should be greater or equal than 1');
  }

  const { day, month, year }: SeparatedDate = getSeparatedDate();

  const dayAgo: Date = new Date(year, month, day - n);
  const monthAgo: Date = new Date(year, month - n, day);
  const yearAgo: Date = new Date(year - n, month, day);

  const daysAgo: Date = dayAgo;
  const monthsAgo: Date = monthAgo;
  const yearsAgo: Date = yearAgo;

  return n > 1
    ? { daysAgo, monthsAgo, yearsAgo }
    : { dayAgo, monthAgo, yearAgo };
};
Enter fullscreen mode Exit fullscreen mode

The Date can be fun too. Learn the basic concepts and just play around with it, you'll like! I hope this post was valuable to you!

Resources

💖 đŸ’Ș 🙅 đŸš©
teekay
TK

Posted on April 15, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related