Why is there a difference with Unix date between 2 and 3 months

You're seeing this behavior because of summer time (daylight saving time).

Because you are currently in summer time, where your clock is one hour ahead, when you ask for three months ago at just after midnight on the first of June, the time ends up being one hour "earlier" because it was not summer time three months ago.

The GNU date documentation suggests to work around this by using 12:00 noon and the 15th of the month as starting points, when asking for relative days or months, respectively. For example:

date +%y-%m-%d --date="$(date +%Y-%m-15) -3 month"

If absolute timing is your primary concern, it's probably best to work off of UTC as it exists for that purpose. Michael's answer is very useful for when you have to work inside of the problem, but it's usually a good idea to avoid it entirely where you can.

When your system isn't set to UTC by default, the simplest way to pass the timezone in is by prefixing your command with the TZ environment variable. This limits the zone switch to a single command and keeps the variable from leaking into your subsequent commands.

$ NOW=$(date '+%s')
$ date -d @$NOW
Wed Jun 11 23:44:35 EDT 2014
$ TZ=UTC date -d @$NOW
Thu Jun 12 03:44:35 UTC 2014

What you shouldn't do is export the TZ variable as this can make things very confusing to troubleshoot, as the following demonstrates.

$ export TZ=UTC
$ date -d @$NOW
Thu Jun 12 03:44:35 UTC 2014
$ TZ=EDT date -d @$NOW
Thu Jun 12 03:44:35 EDT 2014