Path: blob/trunk/third_party/closure/goog/date/relative.js
2868 views
// Copyright 2009 The Closure Library Authors. All Rights Reserved.1//2// Licensed under the Apache License, Version 2.0 (the "License");3// you may not use this file except in compliance with the License.4// You may obtain a copy of the License at5//6// http://www.apache.org/licenses/LICENSE-2.07//8// Unless required by applicable law or agreed to in writing, software9// distributed under the License is distributed on an "AS-IS" BASIS,10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.11// See the License for the specific language governing permissions and12// limitations under the License.1314/**15* @fileoverview Functions for formatting relative dates. Such as "3 days ago"16* "3 hours ago", "14 minutes ago", "12 days ago", "Today", "Yesterday".17*18* For better quality localization of plurals ("hours"/"minutes"/"days") and19* to use local digits, goog.date.relativeWithPlurals can be loaded in addition20* to this namespace.21*22*/2324goog.provide('goog.date.relative');25goog.provide('goog.date.relative.TimeDeltaFormatter');26goog.provide('goog.date.relative.Unit');2728goog.require('goog.i18n.DateTimeFormat');29goog.require('goog.i18n.DateTimePatterns');303132/**33* Number of milliseconds in a minute.34* @type {number}35* @private36*/37goog.date.relative.MINUTE_MS_ = 60000;383940/**41* Number of milliseconds in a day.42* @type {number}43* @private44*/45goog.date.relative.DAY_MS_ = 86400000;464748/**49* Enumeration used to identify time units internally.50* @enum {number}51*/52goog.date.relative.Unit = {53MINUTES: 0,54HOURS: 1,55DAYS: 256};575859/**60* Full date formatter.61* @type {goog.i18n.DateTimeFormat}62* @private63*/64goog.date.relative.fullDateFormatter_;656667/**68* Short time formatter.69* @type {goog.i18n.DateTimeFormat}70* @private71*/72goog.date.relative.shortTimeFormatter_;737475/**76* Month-date formatter.77* @type {goog.i18n.DateTimeFormat}78* @private79*/80goog.date.relative.monthDateFormatter_;818283/**84* @typedef {function(number, boolean, goog.date.relative.Unit): string}85*/86goog.date.relative.TimeDeltaFormatter;878889/**90* Handles formatting of time deltas.91* @private {goog.date.relative.TimeDeltaFormatter}92*/93goog.date.relative.formatTimeDelta_;949596/**97* Sets a different formatting function for time deltas ("3 days ago").98* While its visibility is public, this function is Closure-internal and should99* not be used in application code.100* @param {goog.date.relative.TimeDeltaFormatter} formatter The function to use101* for formatting time deltas (i.e. relative times).102*/103goog.date.relative.setTimeDeltaFormatter = function(formatter) {104goog.date.relative.formatTimeDelta_ = formatter;105};106107108/**109* Returns a date in month format, e.g. Mar 15.110* @param {Date} date The date object.111* @return {string} The formatted string.112* @private113*/114goog.date.relative.formatMonth_ = function(date) {115if (!goog.date.relative.monthDateFormatter_) {116goog.date.relative.monthDateFormatter_ =117new goog.i18n.DateTimeFormat(goog.i18n.DateTimePatterns.MONTH_DAY_ABBR);118}119return goog.date.relative.monthDateFormatter_.format(date);120};121122123/**124* Returns a date in short-time format, e.g. 2:50 PM.125* @param {Date|goog.date.DateTime} date The date object.126* @return {string} The formatted string.127* @private128*/129goog.date.relative.formatShortTime_ = function(date) {130if (!goog.date.relative.shortTimeFormatter_) {131goog.date.relative.shortTimeFormatter_ = new goog.i18n.DateTimeFormat(132goog.i18n.DateTimeFormat.Format.SHORT_TIME);133}134return goog.date.relative.shortTimeFormatter_.format(date);135};136137138/**139* Returns a date in full date format, e.g. Tuesday, March 24, 2009.140* @param {Date|goog.date.DateTime} date The date object.141* @return {string} The formatted string.142* @private143*/144goog.date.relative.formatFullDate_ = function(date) {145if (!goog.date.relative.fullDateFormatter_) {146goog.date.relative.fullDateFormatter_ =147new goog.i18n.DateTimeFormat(goog.i18n.DateTimeFormat.Format.FULL_DATE);148}149return goog.date.relative.fullDateFormatter_.format(date);150};151152153/**154* Accepts a timestamp in milliseconds and outputs a relative time in the form155* of "1 hour ago", "1 day ago", "in 1 hour", "in 2 days" etc. If the date156* delta is over 2 weeks, then the output string will be empty.157* @param {number} dateMs Date in milliseconds.158* @return {string} The formatted date.159*/160goog.date.relative.format = function(dateMs) {161var now = goog.now();162var delta = Math.floor((now - dateMs) / goog.date.relative.MINUTE_MS_);163164var future = false;165166if (delta < 0) {167future = true;168delta *= -1;169}170171if (delta < 60) { // Minutes.172return goog.date.relative.formatTimeDelta_(173delta, future, goog.date.relative.Unit.MINUTES);174175} else {176delta = Math.floor(delta / 60);177if (delta < 24) { // Hours.178return goog.date.relative.formatTimeDelta_(179delta, future, goog.date.relative.Unit.HOURS);180181} else {182// We can be more than 24 hours apart but still only 1 day apart, so we183// compare the closest time from today against the target time to find184// the number of days in the delta.185var midnight = new Date(goog.now());186midnight.setHours(0);187midnight.setMinutes(0);188midnight.setSeconds(0);189midnight.setMilliseconds(0);190191// Convert to days ago.192delta =193Math.ceil((midnight.getTime() - dateMs) / goog.date.relative.DAY_MS_);194195if (future) {196delta *= -1;197}198199// Uses days for less than 2-weeks.200if (delta < 14) {201return goog.date.relative.formatTimeDelta_(202delta, future, goog.date.relative.Unit.DAYS);203204} else {205// For messages older than 2 weeks do not show anything. The client206// should decide the date format to show.207return '';208}209}210}211};212213214/**215* Accepts a timestamp in milliseconds and outputs a relative time in the form216* of "1 hour ago", "1 day ago". All future times will be returned as 0 minutes217* ago.218*219* This is provided for compatibility with users of the previous incarnation of220* the above {@see #format} method who relied on it protecting against221* future dates.222*223* @param {number} dateMs Date in milliseconds.224* @return {string} The formatted date.225*/226goog.date.relative.formatPast = function(dateMs) {227var now = goog.now();228if (now < dateMs) {229dateMs = now;230}231return goog.date.relative.format(dateMs);232};233234235/**236* Accepts a timestamp in milliseconds and outputs a relative day. i.e. "Today",237* "Yesterday", "Tomorrow", or "Sept 15".238*239* @param {number} dateMs Date in milliseconds.240* @param {function(!Date):string=} opt_formatter Formatter for the date.241* Defaults to form 'MMM dd'.242* @return {string} The formatted date.243*/244goog.date.relative.formatDay = function(dateMs, opt_formatter) {245var today = new Date(goog.now());246247today.setHours(0);248today.setMinutes(0);249today.setSeconds(0);250today.setMilliseconds(0);251252var yesterday = new Date(today.getTime() - goog.date.relative.DAY_MS_);253var tomorrow = new Date(today.getTime() + goog.date.relative.DAY_MS_);254var dayAfterTomorrow =255new Date(today.getTime() + 2 * goog.date.relative.DAY_MS_);256257var message;258if (dateMs >= tomorrow.getTime() && dateMs < dayAfterTomorrow.getTime()) {259/** @desc Tomorrow. */260var MSG_TOMORROW = goog.getMsg('Tomorrow');261message = MSG_TOMORROW;262} else if (dateMs >= today.getTime() && dateMs < tomorrow.getTime()) {263/** @desc Today. */264var MSG_TODAY = goog.getMsg('Today');265message = MSG_TODAY;266} else if (dateMs >= yesterday.getTime() && dateMs < today.getTime()) {267/** @desc Yesterday. */268var MSG_YESTERDAY = goog.getMsg('Yesterday');269message = MSG_YESTERDAY;270} else {271// If we don't have a special relative term for this date, then return the272// short date format (or a custom-formatted date).273var formatFunction = opt_formatter || goog.date.relative.formatMonth_;274message = formatFunction(new Date(dateMs));275}276return message;277};278279280/**281* Formats a date, adding the relative date in parenthesis. If the date is less282* than 24 hours then the time will be printed, otherwise the full-date will be283* used. Examples:284* 2:20 PM (1 minute ago)285* Monday, February 27, 2009 (4 days ago)286* Tuesday, March 20, 2005 // Too long ago for a relative date.287*288* @param {Date|goog.date.DateTime} date A date object.289* @param {string=} opt_shortTimeMsg An optional short time message can be290* provided if available, so that it's not recalculated in this function.291* @param {string=} opt_fullDateMsg An optional date message can be292* provided if available, so that it's not recalculated in this function.293* @return {string} The date string in the above form.294*/295goog.date.relative.getDateString = function(296date, opt_shortTimeMsg, opt_fullDateMsg) {297return goog.date.relative.getDateString_(298date, goog.date.relative.format, opt_shortTimeMsg, opt_fullDateMsg);299};300301302/**303* Formats a date, adding the relative date in parenthesis. Functions the same304* as #getDateString but ensures that the date is always seen to be in the past.305* If the date is in the future, it will be shown as 0 minutes ago.306*307* This is provided for compatibility with users of the previous incarnation of308* the above {@see #getDateString} method who relied on it protecting against309* future dates.310*311* @param {Date|goog.date.DateTime} date A date object.312* @param {string=} opt_shortTimeMsg An optional short time message can be313* provided if available, so that it's not recalculated in this function.314* @param {string=} opt_fullDateMsg An optional date message can be315* provided if available, so that it's not recalculated in this function.316* @return {string} The date string in the above form.317*/318goog.date.relative.getPastDateString = function(319date, opt_shortTimeMsg, opt_fullDateMsg) {320return goog.date.relative.getDateString_(321date, goog.date.relative.formatPast, opt_shortTimeMsg, opt_fullDateMsg);322};323324325/**326* Formats a date, adding the relative date in parenthesis. If the date is less327* than 24 hours then the time will be printed, otherwise the full-date will be328* used. Examples:329* 2:20 PM (1 minute ago)330* Monday, February 27, 2009 (4 days ago)331* Tuesday, March 20, 2005 // Too long ago for a relative date.332*333* @param {Date|goog.date.DateTime} date A date object.334* @param {function(number) : string} relativeFormatter Function to use when335* formatting the relative date.336* @param {string=} opt_shortTimeMsg An optional short time message can be337* provided if available, so that it's not recalculated in this function.338* @param {string=} opt_fullDateMsg An optional date message can be339* provided if available, so that it's not recalculated in this function.340* @return {string} The date string in the above form.341* @private342*/343goog.date.relative.getDateString_ = function(344date, relativeFormatter, opt_shortTimeMsg, opt_fullDateMsg) {345var dateMs = date.getTime();346347var relativeDate = relativeFormatter(dateMs);348349if (relativeDate) {350relativeDate = ' (' + relativeDate + ')';351}352353var delta = Math.floor((goog.now() - dateMs) / goog.date.relative.MINUTE_MS_);354if (delta < 60 * 24) {355// TODO(user): this call raises an exception if date is a goog.date.Date.356return (opt_shortTimeMsg || goog.date.relative.formatShortTime_(date)) +357relativeDate;358} else {359return (opt_fullDateMsg || goog.date.relative.formatFullDate_(date)) +360relativeDate;361}362};363364365/*366* TODO(user):367*368* I think that this whole relative formatting should move to DateTimeFormat.369* But we would have to wait for the next version of CLDR, which is cleaning370* the data for relative dates (even ICU has incomplete support for this).371*/372/**373* Gets a localized relative date string for a given delta and unit.374* @param {number} delta Number of minutes/hours/days.375* @param {boolean} future Whether the delta is in the future.376* @param {goog.date.relative.Unit} unit The units the delta is in.377* @return {string} The message.378* @private379*/380goog.date.relative.getMessage_ = function(delta, future, unit) {381var deltaFormatted = goog.i18n.DateTimeFormat.localizeNumbers(delta);382if (!future && unit == goog.date.relative.Unit.MINUTES) {383/**384* @desc Relative date indicating how many minutes ago something happened385* (singular).386*/387var MSG_MINUTES_AGO_SINGULAR =388goog.getMsg('{$num} minute ago', {'num': deltaFormatted});389390/**391* @desc Relative date indicating how many minutes ago something happened392* (plural).393*/394var MSG_MINUTES_AGO_PLURAL =395goog.getMsg('{$num} minutes ago', {'num': deltaFormatted});396397return delta == 1 ? MSG_MINUTES_AGO_SINGULAR : MSG_MINUTES_AGO_PLURAL;398399} else if (future && unit == goog.date.relative.Unit.MINUTES) {400/**401* @desc Relative date indicating in how many minutes something happens402* (singular).403*/404var MSG_IN_MINUTES_SINGULAR =405goog.getMsg('in {$num} minute', {'num': deltaFormatted});406407/**408* @desc Relative date indicating in how many minutes something happens409* (plural).410*/411var MSG_IN_MINUTES_PLURAL =412goog.getMsg('in {$num} minutes', {'num': deltaFormatted});413414return delta == 1 ? MSG_IN_MINUTES_SINGULAR : MSG_IN_MINUTES_PLURAL;415416} else if (!future && unit == goog.date.relative.Unit.HOURS) {417/**418* @desc Relative date indicating how many hours ago something happened419* (singular).420*/421var MSG_HOURS_AGO_SINGULAR =422goog.getMsg('{$num} hour ago', {'num': deltaFormatted});423424/**425* @desc Relative date indicating how many hours ago something happened426* (plural).427*/428var MSG_HOURS_AGO_PLURAL =429goog.getMsg('{$num} hours ago', {'num': deltaFormatted});430431return delta == 1 ? MSG_HOURS_AGO_SINGULAR : MSG_HOURS_AGO_PLURAL;432433} else if (future && unit == goog.date.relative.Unit.HOURS) {434/**435* @desc Relative date indicating in how many hours something happens436* (singular).437*/438var MSG_IN_HOURS_SINGULAR =439goog.getMsg('in {$num} hour', {'num': deltaFormatted});440441/**442* @desc Relative date indicating in how many hours something happens443* (plural).444*/445var MSG_IN_HOURS_PLURAL =446goog.getMsg('in {$num} hours', {'num': deltaFormatted});447448return delta == 1 ? MSG_IN_HOURS_SINGULAR : MSG_IN_HOURS_PLURAL;449450} else if (!future && unit == goog.date.relative.Unit.DAYS) {451/**452* @desc Relative date indicating how many days ago something happened453* (singular).454*/455var MSG_DAYS_AGO_SINGULAR =456goog.getMsg('{$num} day ago', {'num': deltaFormatted});457458/**459* @desc Relative date indicating how many days ago something happened460* (plural).461*/462var MSG_DAYS_AGO_PLURAL =463goog.getMsg('{$num} days ago', {'num': deltaFormatted});464465return delta == 1 ? MSG_DAYS_AGO_SINGULAR : MSG_DAYS_AGO_PLURAL;466467} else if (future && unit == goog.date.relative.Unit.DAYS) {468/**469* @desc Relative date indicating in how many days something happens470* (singular).471*/472var MSG_IN_DAYS_SINGULAR =473goog.getMsg('in {$num} day', {'num': deltaFormatted});474475/**476* @desc Relative date indicating in how many days something happens477* (plural).478*/479var MSG_IN_DAYS_PLURAL =480goog.getMsg('in {$num} days', {'num': deltaFormatted});481482return delta == 1 ? MSG_IN_DAYS_SINGULAR : MSG_IN_DAYS_PLURAL;483484} else {485return '';486}487};488489goog.date.relative.setTimeDeltaFormatter(goog.date.relative.getMessage_);490491492