How to get the meridian (am / pm) with momentsjs and angularjs

10,771

Solution 1

A few things:

  • The word you're looking for is "meridiem", not "meridian". It's a Latin word meaning "mid-day". A.M. is "ante meridiem" (before mid-day) and P.M. is "post meridiem" (after mid-day).

  • The .hours() function and the input array passed to the moment constructor both expect hours of a 24-hour clock, from 0 to 23. There is no built-in function to accept or output a 12-hour clock value, or a meridiem (am/pm).

  • The parsing and formatting functions in moment do have options for 12-hour clock hours (h or hh) and meridiem (A or a), but you're not parsing or formatting in your use case, so they would be a bit clumsy to use. In particular, they are based on moment's current locale settings, so testing a string would be problematic if the locale wasn't fixed to a specific language.

  • You can use these simple functions to convert from 12-hour + meridiem to 24-hour and vice-versa. They are not specific to moment, so you can also use them with the Date object or elsewhere.

    function hours12to24(h, pm) {
        return h == 12 ? pm ? 12 : 0 : pm ? h + 12 : h;
    }
    
    function hours24to12(h) {
        return {
            hour : (h + 11) % 12 + 1,
            pm : h >= 12
        }
    }
    

    To test these functions:

    function test() {
      for (var i = 0; i <= 23; i++) {
        var x = hours24to12(i);
        var h = hours12to24(x.hour, x.pm);
        console.log(i + " == " + x.hour + (x.pm ? " pm" : " am") + " == " + h);
      }
    }
    
  • Also, be careful when calling .month() to get the month number, the results are 0-11, not 1-12. The same is true when building the input array. Your dropdown either needs to use 0-11, or you need to add or subtract 1 accordingly.

Solution 2

You could also do something like m.format('a')

Solution 3

The other answer is very good. It helped me to create my demo because with-out it I would have missed the point with the month (0 to 11).

But you don't need to create the meridiem function because it's already implemented in moment.js. The point is that the usage is not very clear from the docs.

You can use the meridiem function like this:

var curDate = moment().hour(yourHour); // create a moment date with the hour you'd like to test
var meridiem = curDate
            .localeData().meridiem(hour); // will return AM/PM String

If you need a boolean instead of AM/PM string you can pass the AM/PM string to curDate.localeData().isPM(meridiem). That will return true or false.

The point with the month that you don't want to display 0 to 11 in your select can be fixed by a displayLabel function that will increment every value of the month array. Then you'll have it displayed 1 to 12 but stored as 0 to 11. If you're writing a directive that could be better handled by a $parser / $formatter function of ngModel.

Please have a look at the demo below or at this jsfiddle.

angular.module('demoApp', [])
	.filter('momentDate', MomentDateFilter)
	.filter('momentUTC', MomentUTCFilter)
	.controller('MainController', MainController);

function MomentDateFilter() {
	return function(input, format) {
        return moment(input).format(format);
    };
}

function MomentUTCFilter() {
	return function(input, format) {
        return moment.utc(input).format(format);
    };
}

function MainController($scope, $log) {
    var vm = this,
        now = moment();
    
    vm.checkHour = checkHour;
    vm.dateSelect = dateSelection();
    vm.displayLabel = displayLabel;
    vm.now = now;
    vm.selected = {
        "day": 27,
        "month": 8,
        "year": 2015,
        "hour": 18,
        "meridiem": "PM",
        "minutes": 6,
        "seconds": 20
    };
    
    function dateSelection() {
        return { // generated on every page load for demo
           	// better store the generate object as json and load it
            day: createRange(1,31),
            month: createRange(0,11),
            year: createRange(1900, 2100),
            hour: createRange(0,24),
            minutes: createRange(0,59),
            seconds: createRange(0,59),
            meridiem: 'AM_PM'.split('_')
        };
	}
    
    function displayLabel(key, value) {
    	if (key === 'month') {
        	value++; // increment month for correct month value 1 to 12
        }
        return value;
    }
    
    function checkHour(key, hour) { // updates meridiem (AM/PM)
        if (key === 'hour') {
            var curDate = moment().hour(hour);
            //console.log('check hour', hour, curDate.hour(), curDate.localeData());
            vm.selected.meridiem = curDate
                .localeData().meridiem(hour);
            //console.log(curDate
            //    .localeData().isPM(vm.selected.meridiem));
        }
        //console.log('changed', key);
    }
    //console.log(this.dateSelect);
    function createRange(from, to) {
        var arr = [];
    	for(i=from; i<=to; i++) {
        	arr.push(i);
        }
        //console.log(arr);
        return arr;
    }
    
}
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.0-beta.0/angular.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.10.6/moment.js"></script>
<div ng-app="demoApp" ng-controller="MainController as mainCtrl" class="container-fluid">
    <form class="form-inline">
        <div ng-repeat="(key, array) in mainCtrl.dateSelect" class="form-group">
            <label>{{key}}</label><select class="form-control" ng-change="mainCtrl.checkHour(key, array[mainCtrl.selected[key]])" ng-model="mainCtrl.selected[key]" ng-options="value as mainCtrl.displayLabel(key, value) for value in mainCtrl.dateSelect[key]" ng-disabled="key === 'meridiem'">
            </select>
        </div>
    </form>
    selected: <pre>{{mainCtrl.selected|json}}</pre>
    raw date (unformatted, UTC): {{mainCtrl.selected | momentUTC}}<br/>
    date formatted (meridiem locale): {{mainCtrl.selected | momentUTC : 'LLLL' }}<br/> 
    
    
    now (momentjs) {{mainCtrl.now}}
</div>
Share:
10,771
ojedawinder
Author by

ojedawinder

Updated on July 10, 2022

Comments

  • ojedawinder
    ojedawinder almost 2 years

    I have a question about the library moments.js, I have an application in angularjs where I have six select elements for the year, month, day, hour, minutes, and am / pm format. I am using the following format moment to build m.format date ('YYYY-MM-DD hh: mm: ss a).

    The code is as follows:

    var m = moment([scope.val.year, scope.val.month, scope.val.date, scope.val.hour, scope.val.minute]); //build date 
    
    model.$setViewValue(m.format('YYYY-MM-DD hh:mm:ss a'))
    

    I get the data m.hour (), m.minute (), etc but there is a way to get the am / pm format, I have not found anything about it, something perhaps as m.meridian () => "am "or" pm ".

    I would build the array passing as parameter if am or pm, and then also would get from any date if am or pm date.

  • ojedawinder
    ojedawinder over 8 years
    Thanks, it was very useful to me your solution.
  • ojedawinder
    ojedawinder over 8 years
    Thanks for your help, the solution of Matt and your solution worked for me.