PrimeFaces <p:calendar> has some good attributes like mindate and maxdate, considering we have two calendars “start” and “end”, to validate a proper date range between them, we can set the mindate of “end” calendar to the value of “start” calendar. This will do a good job, but not a complete one, still when you edit it manually, you can pass through, and the range will be false.
To achieve that, we’re going to implement a custom JSF 2 date validator.

The .xhtml page code will have:

From: 
<p:calendar id="startDate" value="#{myBean.startDate}" showOn="button" 
    popupIconOnly="false" pattern ="EEE, dd MMM, yyyy" locale="en" 
    required="true" requiredMessage="#{fnc:formatMessage('msg.required', myBean.locale, ui['calendar.start'])}"
    label="#{ui['calendar.start']}">
    <p:ajax event="dateSelect" update="endDate" />
</p:calendar>

To:
<p:calendar id="endDate" value="#{myBean.endDate}" showOn="button" 
    mindate="#{myBean.startDate}" popupIconOnly="false" pattern="EEE, dd MMM, yyyy" 
    locale="en" required="true" label="#{ui['calendar.end']}" 
    requiredMessage="#{fnc:formatMessage('msg.required', myBean.locale, ui['calendar.end'])}">
    <f:attribute name="startDate" value="#{myBean.startDate}" />
    <f:validator validatorId="primeDateRangeValidator" />
</p:calendar>

And the PrimeDateRangeValidator.java will be:

package myproject.view.validators;

import java.util.Date;

import javax.faces.application.FacesMessage;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.validator.FacesValidator;
import javax.faces.validator.Validator;
import javax.faces.validator.ValidatorException;

import myproject.view.util.FacesMessageUtil;

import org.primefaces.component.calendar.Calendar;

@FacesValidator("primeDateRangeValidator")
public class PrimeDateRangeValidator implements Validator {
	
	@Override
	public void validate(FacesContext context, UIComponent component, Object value) throws ValidatorException {
		if (value == null) {
			return;
		}
		
		//Leave the null handling of startDate to required="true"
		Object startDateValue = component.getAttributes().get("startDate");
		if (startDateValue==null) {
			return;
		}
		
		Date startDate = (Date)startDateValue;
		Date endDate = (Date)value; 
		if (endDate.before(startDate)) {
			throw new ValidatorException(
					FacesMessageUtil.newBundledFacesMessage(FacesMessage.SEVERITY_ERROR, "", "msg.dateRange", ((Calendar)component).getLabel(), startDate));
		}
	}
}

Every time the a new value of startDate is selected, endDate calendar will be updated, so <f:attribute name="startDate" value="#{myBean.startDate}" /> will be updated too, on validation phase PrimeDateRangeValidator will fetch the value from component.getAttributes().get("startDate").
a normal comparison between the two dates:

if (endDate.before(startDate))

will throw new ValidatorException to inform the jsf page if validation phase failed.
For more reference your .properties bundle file will have these entries:

msg.dateRange=Date "{0}" should be after "{1}".
msg.required="{0}" value required.
calendar.start=Start Date
calendar.end=End Date

You can get the FacesMessageUtil class from Java Localization with PropertyResourceBundle.
See also Parametrized Messages in JSF with Facelets Taglib Functions to see how requiredMessage parameterized message was implemented.

This tutorial was created using: JSF v2.1.0-b03 and PrimeFaces v3.5.